home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Linux / Kubuntu 8.10 / kubuntu-8.10-desktop-i386.iso / casper / filesystem.squashfs / usr / share / perl5 / Foomatic / DB.pm next >
Text File  |  2008-08-19  |  173KB  |  5,647 lines

  1.  
  2. package Foomatic::DB;
  3. use Exporter;
  4. use Encode;
  5. @ISA = qw(Exporter);
  6.  
  7. @EXPORT_OK = qw(normalizename comment_filter
  8.         get_overview
  9.         getexecdocs
  10.         translate_printer_id
  11.         );
  12. @EXPORT = qw(ppdtoperl ppdfromvartoperl);
  13.  
  14. use Foomatic::Defaults qw(:DEFAULT $DEBUG);
  15. use Data::Dumper;
  16. use POSIX;                      # for rounding integers
  17. use strict;
  18.  
  19. my $ver = '$Revision$ ';
  20.  
  21. # constructor for Foomatic::DB
  22. sub new {
  23.     my $type = shift(@_);
  24.     my $this = bless {@_}, $type;
  25.     $this->{'language'} = "C";
  26.     return $this;
  27. }
  28.  
  29. # A map from the database's internal one-letter driver types to English
  30. my %driver_types = ('F' => 'Filter',
  31.             'P' => 'Postscript',
  32.             'U' => 'Ghostscript Uniprint',
  33.             'G' => 'Ghostscript');
  34.  
  35. # Translate old numerical PostGreSQL printer IDs to the new clear text ones.
  36. sub translate_printer_id {
  37.     my ($oldid) = @_;
  38.     # Read translation table for the printer IDs
  39.     my $translation_table = "$libdir/db/oldprinterids";
  40.     open TRTAB, "< $translation_table" or return $oldid;
  41.     while (<TRTAB>) {
  42.     chomp;
  43.     my $searcholdid = quotemeta($oldid);
  44.     if (/^\s*$searcholdid\s+(\S+)\s*$/) {
  45.         # ID found, return new ID
  46.         my $newid = $1;
  47.         close TRTAB;
  48.         return $newid;
  49.     }
  50.     }
  51.     # ID not found, return original one
  52.     close TRTAB;
  53.     return $oldid;
  54. }
  55.  
  56. # Set language for localized answers
  57. sub set_language {
  58.     my ($this, $language) = @_;
  59.     $this->{'language'} = $language;
  60. }
  61.  
  62. # List of driver names
  63. sub get_driverlist {
  64.     my ($this) = @_;
  65.     return $this->_get_xml_filelist('source/driver');
  66. }
  67.  
  68. # List of printer id's
  69. sub get_printerlist {
  70.     my ($this) = @_;
  71.     return $this->_get_xml_filelist('source/printer');
  72. }
  73.  
  74. sub get_overview {
  75.     my ($this, $rebuild, $cupsppds) = @_;
  76.  
  77.     # In-memory cache only for this process
  78.     return $this->{'overview'} if defined($this->{'overview'}) &&
  79.     !$rebuild;
  80.     $this->{'overview'} = undef;
  81.  
  82.     # Read on-disk cache file if we have one
  83.     if (defined($this->{'overviewfile'})) {
  84.         if (!$rebuild && (-r $this->{'overviewfile'})) {
  85.         if (open CFILE, "< $this->{'overviewfile'}") {
  86.         my $output = join('', <CFILE>);
  87.         close CFILE;
  88.         # Only output the cashed page if it was really
  89.         # completely written Before introduction of this
  90.         # measure pages would not display due to an incomplete
  91.         # cache file until the next page rebuild (or until
  92.         # manually nuking the cache).
  93.         if ($output =~ m!\]\;\s*$!s) {
  94.             my $VAR1;
  95.             if (eval $output) {
  96.             $this->{'overview'} = $VAR1;
  97.             return $this->{'overview'};
  98.             }
  99.         }
  100.         }
  101.     }
  102.     }
  103.  
  104.     # Build a new overview
  105.     my $otype = ($cupsppds ? '-C' : '-O');
  106.     $otype .= ' -n' if ($cupsppds == 1);
  107.     # Generate overview Perl data structure from database
  108.     my $VAR1;
  109.     eval `$bindir/foomatic-combo-xml $otype -l '$libdir' | $bindir/foomatic-perl-data -O -l $this->{'language'}` ||
  110.     die ("Could not run \"foomatic-combo-xml\"/\"foomatic-perl-data\"!");
  111.     $this->{'overview'} = $VAR1;
  112.  
  113.     # Write on-disk cache file if we have one
  114.     if (defined($this->{'overviewfile'})) {
  115.     if (open CFILE, "> $this->{'overviewfile'}") {
  116.         print CFILE Dumper($this->{'overview'});
  117.         close CFILE;
  118.     }
  119.     }
  120.  
  121.     return $this->{'overview'};
  122. }
  123.  
  124. sub get_overview_xml {
  125.     my ($this, $compile) = @_;
  126.  
  127.     open( FCX, "$bindir/foomatic-combo-xml -O -l '$libdir'|")
  128.     or die "Can't execute $bindir/foomatic-combo-xml -O -l '$libdir'";
  129.     $_ = join('', <FCX>);
  130.     close FCX;
  131.     return $_;
  132. }
  133.  
  134. sub get_combo_data_xml {
  135.     my ($this, $drv, $poid, $withoptions) = @_;
  136.  
  137.     # Insert the default option settings if there are some and the user
  138.     # desires it.
  139.     my $options = "";
  140.     if (($withoptions) && (defined($this->{'dat'}))) {
  141.     my $dat = $this->{'dat'};
  142.     for my $arg (@{$dat->{'args'}}) {
  143.         my $name = $arg->{'name'};
  144.         my $default = $arg->{'default'};
  145.         if (($name) && ($default)) {
  146.         $options .= " -o '$name'='$default'";
  147.         }
  148.     }
  149.     }
  150.  
  151.     open( FCX, "$bindir/foomatic-combo-xml -d '$drv' -p '$poid'$options -l '$libdir'|")
  152.     or die "Can't execute $bindir/foomatic-combo-xml -d '$drv' -p '$poid'$options -l '$libdir'";
  153.     $_ = join('', <FCX>);
  154.     close FCX;
  155.     return $_;
  156. }
  157.  
  158. sub get_printer {
  159.     my ($this, $poid) = @_;
  160.     # Generate printer Perl data structure from database
  161.     my $VAR1;
  162.     if (-r "$libdir/db/source/printer/$poid.xml") {
  163.     eval (`$bindir/foomatic-perl-data -P -l $this->{'language'} '$libdir/db/source/printer/$poid.xml'`) ||
  164.         die ("Could not run \"foomatic-perl-data\"!");
  165.     } else {
  166.     my ($make, $model);
  167.     if ($poid =~ /^([^\-]+)\-(.*)$/) {
  168.         $make = $1;
  169.         $model = $2;
  170.         $make =~ s/_/ /g;
  171.         $model =~ s/_/ /g;
  172.     } else {
  173.         $make = $poid;
  174.         $make =~ s/_/ /g;
  175.         $model = "Unknown model";
  176.     }
  177.     $VAR1 = {
  178.         'id' => $poid,
  179.         'make' => $make,
  180.         'model' => $model,
  181.         'noxmlentry' => 1
  182.     }
  183.     }
  184.     return $VAR1;
  185. }
  186.  
  187. sub printer_exists {
  188.     my ($this, $poid) = @_;
  189.     # Check whether a printer XML file exists in the database
  190.     return 1 if (-r "$libdir/db/source/printer/$poid.xml");
  191.     return undef;
  192. }
  193.  
  194. sub get_printer_xml {
  195.     my ($this, $poid) = @_;
  196.     return $this->_get_object_xml("source/printer/$poid", 1);
  197. }
  198.  
  199. sub get_driver {
  200.     my ($this, $drv) = @_;
  201.     # Generate driver Perl data structure from database
  202.     my $VAR1;
  203.     if (-r "$libdir/db/source/driver/$drv.xml") {
  204.     eval (`$bindir/foomatic-perl-data -D -l $this->{'language'} '$libdir/db/source/driver/$drv.xml'`) ||
  205.         die ("Could not run \"foomatic-perl-data\"!");
  206.     } else {
  207.     return undef;
  208.     }
  209.     return $VAR1;
  210. }
  211.  
  212. sub get_driver_xml {
  213.     my ($this, $drv) = @_;
  214.     return $this->_get_object_xml("source/driver/$drv", 1);
  215. }
  216.  
  217. # Utility query function sorts of things:
  218.  
  219. sub get_printers_for_driver {
  220.     my ($this, $drv) = @_;
  221.  
  222.     my $driver = $this->get_driver($drv);
  223.  
  224.     if (!defined($driver)) {return undef;}
  225.  
  226.     return map { $_->{'id'} } @{$driver->{'printers'}};
  227. }
  228.  
  229. # Routine lookup; just examine the overview
  230. sub get_drivers_for_printer {
  231.     my ($this, $printer) = @_;
  232.  
  233.     my @drivers = ();
  234.  
  235.     my $over = $this->get_overview();
  236.  
  237.     my $p;
  238.     for $p (@{$over}) {
  239.     if ($p->{'id'} eq $printer) {
  240.         return @{$p->{'drivers'}};
  241.     }
  242.     }
  243.  
  244.     return undef;
  245. }
  246.  
  247.  
  248. # Clean some manufacturer's names (for printer search function, taken
  249. # from printerdrake, printer setup tool of Mandriva Linux)
  250. sub clean_manufacturer_name {
  251.     my ($make) = @_;
  252.     $make =~ s/^Canon\W.*$/Canon/i;
  253.     $make =~ s/^Lexmark.*$/Lexmark/i;
  254.     $make =~ s/^Hewlett?[_\s\-]*Packard/HP/i;
  255.     $make =~ s/^Seiko[_\s\-]*Epson/Epson/i;
  256.     $make =~ s/^Kyocera[_\s\-]*Mita/Kyocera/i;
  257.     $make =~ s/^CItoh/C.Itoh/i;
  258.     $make =~ s/^Oki(|[_\s\-]*Data)\s*$/Oki/i;
  259.     $make =~ s/^(SilentWriter2?|ColorMate)/NEC/i;
  260.     $make =~ s/^(XPrint|Majestix)/Xerox/i;
  261.     $make =~ s/^QMS-PS/QMS/i;
  262.     $make =~ s/^konica([_\s\-]|)minolta/KONICA MINOLTA/i;
  263.     $make =~ s/^(Personal|LaserWriter)/Apple/i;
  264.     $make =~ s/^Digital/DEC/i;
  265.     $make =~ s/\s+Inc\.//i;
  266.     $make =~ s/\s+Corp\.//i;
  267.     $make =~ s/\s+SA\.//i;
  268.     $make =~ s/\s+S\.\s*A\.//i;
  269.     $make =~ s/\s+Ltd\.//i;
  270.     $make =~ s/\s+International//i;
  271.     $make =~ s/\s+Int\.//i;
  272.     return $make;
  273. }    
  274.  
  275. # Guess manufacturer by description with only model name (for printer
  276. # search function, taken from printerdrake, printer setup tool of
  277. # Mandriva Linux)
  278.  
  279. sub guessmake {
  280.  
  281.     my ($description) = @_;
  282.  
  283.     my $manufacturer;
  284.     my $model;
  285.  
  286.     if ($description =~
  287.     /^\s*(DeskJet|LaserJet|OfficeJet|PSC|PhotoSmart)\b/i) {
  288.     # HP printer
  289.     $manufacturer = "HP";
  290.     $model = $description;
  291.     } elsif ($description =~
  292.          /^\s*(Stylus|EPL|AcuLaser)\b/i) {
  293.     # Epson printer
  294.     $manufacturer = "Epson";
  295.     $model = $description;
  296.     } elsif ($description =~
  297.          /^\s*(Aficio)\b/i) {
  298.     # Ricoh printer
  299.     $manufacturer = "Ricoh";
  300.     $model = $description;
  301.     } elsif ($description =~
  302.          /^\s*(Optra|Color\s+JetPrinter)\b/i) {
  303.     # Lexmark printer
  304.     $manufacturer = "Lexmark";
  305.     $model = $description;
  306.     } elsif ($description =~
  307.          /^\s*(imageRunner|Pixma|Pixus|BJC|LBP)\b/i) {
  308.     # Canon printer
  309.     $manufacturer = "Canon";
  310.     $model = $description;
  311.     } elsif ($description =~
  312.          /^\s*(Phaser|DocuPrint|(Work|Document)\s*(Home|)Centre)\b/i) {
  313.     # Xerox printer
  314.     $manufacturer = "Xerox";
  315.     $model = $description;
  316.     } elsif (($description =~ /^\s*(KONICA\s*MINOLTA)\s+(\S.*)$/i) ||
  317.          ($description =~ /^\s*(\S*)\s+(\S.*)$/)) {
  318.     $manufacturer = $1 if $manufacturer eq "";
  319.     $model = $2 if $model eq "";
  320.     }
  321.     return ($manufacturer, $model);
  322. }
  323.  
  324. # Normalize a string, so that for a search only letters
  325. # (case-insensitive), numbers and boundaries between letter blocks and
  326. # number blocks are considered. The pipe '|' as separator between make
  327. # and model is also considered. Blocks of other characters are
  328. # replaced by a single space and boundaries between letters and
  329. # numbers are marked with a single space.
  330. sub normalize {
  331.     my ($str) = @_;
  332.     $str = lc($str);
  333.     $str =~ s/\+/plus/g;
  334.     $str =~ s/[^a-z0-9\|]+/ /g;
  335.     $str =~ s/(?<=[a-z])(?=[0-9])/ /g;
  336.     $str =~ s/(?<=[0-9])(?=[a-z])/ /g;
  337.     return $str;
  338. }
  339.  
  340. # Find a printer in the database based on an auto-detected device ID
  341. # or a user-typed search term
  342. sub find_printer {
  343.     my ($this, $searchterm, $mode, $output) = @_;
  344.     # $mode = 0: Everything (default)
  345.     # $mode = 1: No matches on only the manufacturer
  346.     # $mode = 2: No matches on only the manufacturer or only the model
  347.     # $mode = 3: Exact matches of device ID, make/model, or Foomatic ID
  348.     #            plus matches of the page description language
  349.     # $mode = 4: Exact matches of device ID, make/model, or Foomatic ID
  350.     #            only
  351.     # $output = 0: Everything
  352.     # $output = 1: Only best match class (default)
  353.     # $output = 2: Only best match
  354.  
  355.     # Correct options
  356.     $mode = 0 if !defined $mode;
  357.     $mode = 0 if $mode < 0;
  358.     $mode = 4 if $mode > 4;
  359.     $output = 1 if !defined $output;
  360.     $output = 0 if $output < 0;
  361.     $output = 2 if $output > 2;
  362.  
  363.     my $over = $this->get_overview();
  364.  
  365.     my %results;
  366.  
  367.     # Parse the search term
  368.     my ($automake, $automodel, $autodescr, $autocmdset, $autosku);
  369.     my $deviceid = 0;
  370.  
  371.     # Do we have a device ID?
  372.     if ($searchterm =~ /(MFG|MANUFACTURER):([^;]+);/) {
  373.     $automake = $2;
  374.     $deviceid = 1;
  375.     }
  376.     if ($searchterm =~ /(MDL|MODEL):([^;]+);/) {
  377.     $automodel = $2;
  378.     $automodel =~ s/\s+$//;
  379.     $deviceid = 1;
  380.     }
  381.     if ($searchterm =~ /(DES|DESCRIPTION):([^;]+);/) {
  382.     $autodescr = $2;
  383.     $autodescr =~ s/\s+$//;
  384.     $deviceid = 1;
  385.     }
  386.     if ($searchterm =~ /(CMD|COMMAND\s?SET):([^;]+);/) {
  387.     $autocmdset = $2;
  388.     $deviceid = 1;
  389.     }
  390.     if ($searchterm =~ /(SKU):([^;]+);/) {
  391.     $autosku = $2;
  392.     $autosku =~ s/\s+$//;
  393.     $deviceid = 1;
  394.     }
  395.  
  396.     # Search term is not a device ID
  397.     if (!$deviceid) {
  398.     if ($searchterm =~ /^([^\|]+)\|([^\|]+|)(\|.*?|)$/) {
  399.         $automake = $1;
  400.         $automodel = $2;
  401.     } else {
  402.         $autodescr = $searchterm;
  403.     }
  404.     }
  405.  
  406.     # This is the algorithm used in printerdrake (printer setup tool
  407.     # of Mandriva Linux) to match results of the printer auto-detection
  408.     # with the printer database
  409.  
  410.     # Clean some manufacturer's names
  411.     my $descrmake = clean_manufacturer_name($automake);
  412.  
  413.     # Generate data to match human-readable make/model names
  414.     # of Foomatic database
  415.     my $descr;
  416.     if ($automake && $autosku) {
  417.     $descr = "$descrmake|$autosku";
  418.     } elsif ($automake && $automodel) {
  419.     $descr = "$descrmake|$automodel";
  420.     } elsif ($autodescr && (length($autodescr) > 5)) {
  421.     my ($mf, $md) =
  422.         guessmake($autodescr);
  423.     $descrmake = clean_manufacturer_name($mf);
  424.     $descr = "$descrmake|$md";
  425.     } elsif ($automodel) {
  426.     my ($mf, $md) =
  427.         guessmake($automodel);
  428.     $descrmake = clean_manufacturer_name($mf);
  429.     $descr = "$descrmake|$md";
  430.     } elsif ($automake) {
  431.     $descr = "$descrmake|";
  432.     }
  433.  
  434.     # Remove manufacturer's name from the beginning of the
  435.     # description (do not do this with manufacturer names which
  436.     # contain odd characters)
  437.     $descr =~ s/^$descrmake\|\s*$descrmake\s*/$descrmake|/i
  438.     if $descrmake && 
  439.     $descrmake !~ m![\\/\(\)\[\]\|\.\$\@\%\*\?]!;
  440.  
  441.     # Clean up the description from noise which makes the best match
  442.     # difficult
  443.     $descr =~ s/\s+[Ss]eries//i;
  444.     $descr =~ s/\s+\(?[Pp]rinter\)?$//i;
  445.  
  446.     # Try to find an exact match, check both whether the detected
  447.     # make|model is in the make|model of the database entry and vice versa
  448.     # If there is more than one matching database entry, the longest match
  449.     # counts.
  450.     my $matchlength = -1000;
  451.     my $bestmatchlength = -1000;
  452.     my $p;
  453.   DBENTRY: for $p (@{$over}) {
  454.     # Try to match the device ID string of the auto-detection
  455.     if ($p->{make} =~ /Generic/i) {
  456.         # Database entry for generic printer, check printer
  457.         # languages (command set)
  458.         if ($p->{model} =~ m!PCL\s*5/5e!i) {
  459.         # Generic PCL 5/5e Printer
  460.         if ($autocmdset =~
  461.             /(^|[:,])PCL\s*\-*\s*(5|)($|[,;])/i) {
  462.             $matchlength = 70;
  463.             $bestmatchlength = $matchlength if
  464.             $bestmatchlength < $matchlength;
  465.             $results{$p->{id}} = $matchlength if
  466.             (!defined($results{$p->{id}}) ||
  467.              ($results{$p->{id}} < $matchlength));
  468.             next;
  469.         }
  470.         } elsif ($p->{model} =~ m!PCL\s*(6|XL)!i) {
  471.         # Generic PCL 6/XL Printer
  472.         if ($autocmdset =~
  473.             /(^|[:,])PCL\s*\-*\s*(6|XL)($|[,;])/i) {
  474.             $matchlength = 80;
  475.             $bestmatchlength = $matchlength if
  476.             $bestmatchlength < $matchlength;
  477.             $results{$p->{id}} = $matchlength if
  478.             (!defined($results{$p->{id}}) ||
  479.              ($results{$p->{id}} < $matchlength));
  480.             next;
  481.         }
  482.         } elsif ($p->{model} =~ m!(PostScript)!i) {
  483.         # Generic PostScript Printer
  484.         if ($autocmdset =~
  485.             /(^|[:,])(PS|POSTSCRIPT)[^:;,]*($|[,;])/i) {
  486.             $matchlength = 90;
  487.             $bestmatchlength = $matchlength if
  488.             $bestmatchlength < $matchlength;
  489.             $results{$p->{id}} = $matchlength if
  490.             (!defined($results{$p->{id}}) ||
  491.              ($results{$p->{id}} < $matchlength));
  492.             next;
  493.         }
  494.         }
  495.  
  496.     } else {
  497.         # "Real" manufacturer, check manufacturer, model, and/or
  498.         # description
  499.         my $matched = 1;
  500.         my ($mfg, $mdl, $des, $sku);
  501.         my $ieee1284 = deviceIDfromDBEntry($p);
  502.         if ($ieee1284 =~ /(MFG|MANUFACTURER):([^;]+);/) {
  503.         $mfg = $2;
  504.         }
  505.         if ($ieee1284 =~ /(MDL|MODEL):([^;]+);/) {
  506.         $mdl = $2;
  507.         $mdl =~ s/\s+$//;
  508.         }
  509.         if ($ieee1284 =~ /(DES|DESCRIPTION):([^;]+);/) {
  510.         $des = $2;
  511.         $des =~ s/\s+$//;
  512.         }
  513.         if ($ieee1284 =~ /(SKU):([^;]+);/) {
  514.         $sku = $2;
  515.         $sku =~ s/\s+$//;
  516.         }
  517.         if ($mfg) {
  518.         if ($mfg ne $automake) {
  519.             $matched = 0;
  520.         }
  521.         }
  522.         if ($mdl) {
  523.         if ($mdl ne $automodel) {
  524.             $matched = 0;
  525.         }
  526.         }
  527.         if ($des) {
  528.         if ($des ne $autodescr) {
  529.             $matched = 0;
  530.         }
  531.         }
  532.         if ($sku && $autosku) {
  533.         if ($sku ne $autosku) {
  534.             $matched = 0;
  535.         }
  536.         }
  537.         if ($matched &&
  538.         ($des || ($mfg && ($mdl || ($sku && $autosku))))) {
  539.         # Full match to known auto-detection data
  540.         $matchlength = 1000;
  541.         $bestmatchlength = $matchlength if
  542.             $bestmatchlength < $matchlength;
  543.         $results{$p->{id}} = $matchlength if
  544.                 (!defined($results{$p->{id}}) ||
  545.                  ($results{$p->{id}} < $matchlength)); 
  546.         next;
  547.         }
  548.     }
  549.  
  550.     # Try to match the (human-readable) make and model of the
  551.     # Foomatic database or of the PPD file
  552.     my $dbmakemodel = "$p->{make}|$p->{model}";
  553.  
  554.     # At first try to match make and model, then only model and
  555.     # after that only make
  556.     my $searchtasks = [[$descr, $dbmakemodel, 0],
  557.                [$searchterm, $p->{model}, -200],
  558.                [clean_manufacturer_name($searchterm),
  559.                 $p->{make}, -300],
  560.                [$searchterm, $p->{id}, 0]];
  561.  
  562.     foreach my $task (@{$searchtasks}) {
  563.  
  564.         # Do not try to match search terms or database entries without
  565.         # real content
  566.         next unless $task->[0] =~ /[a-z]/i;
  567.         next unless $task->[1] =~ /[a-z]/i;
  568.  
  569.         # If make and model match exactly, we have found the correct
  570.         # entry and we can stop searching human-readable makes and
  571.         # models
  572.         if (normalize($task->[1]) eq normalize($task->[0])) {
  573.         $matchlength = 100;
  574.         $bestmatchlength = $matchlength + $task->[2] if
  575.             $bestmatchlength < $matchlength + $task->[2];
  576.         $results{$p->{id}} = $matchlength + $task->[2] if
  577.                 (!defined($results{$p->{id}}) ||
  578.                  ($results{$p->{id}} < $matchlength)); 
  579.         next DBENTRY;
  580.         }
  581.  
  582.         # Matching a part of the human-readable makes and models
  583.         # should only be done if the search term is not the name of
  584.         # an old model, otherwise the newest, not yet listed models
  585.         # match with the oldest model of the manufacturer (as the
  586.         # Epson Stylus Photo 900 with the original Epson Stylus Photo)
  587.         my @badsearchterms = 
  588.         ("HP|DeskJet",
  589.          "HP|LaserJet",
  590.          "HP|DesignJet",
  591.          "HP|OfficeJet",
  592.          "HP|PhotoSmart",
  593.          "EPSON|Stylus",
  594.          "EPSON|Stylus Color",
  595.          "EPSON|Stylus Photo",
  596.          "EPSON|Stylus Pro",
  597.          "XEROX|WorkCentre",
  598.          "XEROX|DocuPrint");
  599.         if (!member($task->[0], @badsearchterms)) {
  600.         my $searcht = normalize($task->[0]);
  601.         my $lsearcht = length($searcht);
  602.         $searcht =~ s!([\\/\(\)\[\]\|\.\$\@\%\*\?])!\\$1!g;
  603.         my $s = normalize($task->[1]);
  604.         if ((1 || $lsearcht >= $matchlength) &&
  605.             $s =~ m!$searcht!i) {
  606.             $matchlength = $lsearcht;
  607.             $bestmatchlength = $matchlength + $task->[2] if
  608.             $bestmatchlength < $matchlength + $task->[2];
  609.             $results{$p->{id}} = $matchlength + $task->[2] if
  610.                 (!defined($results{$p->{id}}) ||
  611.                  ($results{$p->{id}} < $matchlength)); 
  612.         }
  613.         }
  614.         if (!member($task->[1], @badsearchterms)) {
  615.         my $searcht = normalize($task->[1]);
  616.         my $lsearcht = length($searcht);
  617.         $searcht =~ s!([\\/\(\)\[\]\|\.\$\@\%\*\?])!\\$1!g;
  618.         my $s = normalize($task->[0]);
  619.         if ((1 || $lsearcht >= $matchlength) &&
  620.             $s =~ m!$searcht!i) {
  621.             $matchlength = $lsearcht;
  622.             $bestmatchlength = $matchlength + $task->[2] if
  623.             $bestmatchlength < $matchlength + $task->[2];
  624.             $results{$p->{id}} = $matchlength + $task->[2] if
  625.                 (!defined($results{$p->{id}}) ||
  626.                  ($results{$p->{id}} < $matchlength)); 
  627.         }
  628.         }
  629.     }
  630.     }
  631.  
  632.     return grep {
  633.     ((($mode == 4) && ($results{$_} >= 100)) ||
  634.      (($mode == 3) && ($results{$_} > 60)) ||
  635.      (($mode == 2) && ($results{$_} > -100)) ||
  636.      (($mode == 1) && ($results{$_} > -200)) ||
  637.      ($mode == 0)) &&
  638.     (($output == 0) ||
  639.      (($output == 1) &&
  640.       !((($bestmatchlength >= 100) && ($results{$_} < 100)) || 
  641.         (($bestmatchlength >= 60) && ($results{$_} < 60)) || 
  642.         (($bestmatchlength >= 0) && ($results{$_} < 0)) || 
  643.         (($bestmatchlength >= -100) && ($results{$_} < -100)) || 
  644.         (($bestmatchlength >= -200) && ($results{$_} < -200)) || 
  645.         (($bestmatchlength >= -300) && ($results{$_} < -300)) || 
  646.         (($bestmatchlength >= -400) && ($results{$_} < -400)))) ||
  647.      (($output == 2) &&
  648.       ($results{$_} == $bestmatchlength)))
  649.     } sort { $results{$b} <=> $results{$a} } keys(%results);
  650. }
  651.  
  652. # This function sorts the options at first by their group membership and
  653. # then by their names appearing in the list of functional areas. This way
  654. # it will be made easier to build the PPD file with option groups and in
  655. # user interfaces options will appear sorted by their functionality.
  656. sub sortargs {
  657.  
  658.     # All sorting done case-insensitive and characters which are not a
  659.     # letter or number are taken out!!
  660.  
  661.     # List of typical option names to appear at first
  662.     # The terms must fit to the beginning of the line, terms which must fit
  663.     # exactly must have '\$' in the end.
  664.     my @standardopts = (
  665.             # The most important composite option
  666.             "printoutmode",
  667.             # Options which appear in the "General" group in 
  668.             # CUPS and similar media handling options
  669.             "pagesize",
  670.             "papersize",
  671.             "mediasize",
  672.             "inputslot",
  673.             "papersource",
  674.             "mediasource",
  675.             "sheetfeeder",
  676.             "mediafeed",
  677.             "paperfeed",
  678.             "manualfeed",
  679.             "manual",
  680.             "outputtray",
  681.             "outputslot",
  682.             "outtray",
  683.             "faceup",
  684.             "facedown",
  685.             "mediatype",
  686.             "papertype",
  687.             "mediaweight",
  688.             "paperweight",
  689.             "duplex",
  690.             "sides",
  691.             "binding",
  692.             "tumble",
  693.             "notumble",
  694.             "media",
  695.             "paper",
  696.             # Other hardware options
  697.             "inktype",
  698.             "ink",
  699.             # Page choice/ordering options
  700.             "pageset",
  701.             "pagerange",
  702.             "pages",
  703.             "nup",
  704.             "numberup",
  705.             # Printout quality, colour/bw
  706.             "resolution",
  707.             "gsresolution",
  708.             "hwresolution",
  709.             "jclresolution",
  710.             "fastres",
  711.             "jclfastres",
  712.             "quality",
  713.             "printquality",
  714.             "printingquality",
  715.             "printoutquality",
  716.             "bitsperpixel",
  717.             "econo",
  718.             "jclecono",
  719.             "tonersav",
  720.             "photomode",
  721.             "photo",
  722.             "colormode",
  723.             "colourmode",
  724.             "color",
  725.             "colour",
  726.             "grayscale",
  727.             "gray",
  728.             "monochrome",
  729.             "mono",
  730.             "blackonly",
  731.             "colormodel",
  732.             "colourmodel",
  733.             "processcolormodel",
  734.             "processcolourmodel",
  735.             "printcolors",
  736.             "printcolours",
  737.             "outputtype",
  738.             "outputmode",
  739.             "printingmode",
  740.             "printoutmode",
  741.             "printmode",
  742.             "mode",
  743.             "imagetype",
  744.             "imagemode",
  745.             "image",
  746.             "dithering",
  747.             "dither",
  748.             "halftoning",
  749.             "halftone",
  750.             "floydsteinberg",
  751.             "ret\$",
  752.             "cret\$",
  753.             "photoret\$",
  754.             "smooth",
  755.             # Adjustments
  756.             "gammacorrection",
  757.             "gammacorr",
  758.             "gammageneral",
  759.             "mastergamma",
  760.             "stpgamma",
  761.             "gammablack",
  762.             "blackgamma",
  763.             "gammacyan",
  764.             "cyangamma",
  765.             "gammamagenta",
  766.             "magentagamma",
  767.             "gammayellow",
  768.             "yellowgamma",
  769.             "gammared",
  770.             "redgamma",
  771.             "gammagreen",
  772.             "greengamma",
  773.             "gammablue",
  774.             "bluegamma",
  775.             "gamma",
  776.             "density",
  777.             "stpdensity",
  778.             "hpljdensity",
  779.             "tonerdensity",
  780.             "inkdensity",
  781.             "brightness",
  782.             "stpbrightness",
  783.             "saturation",
  784.             "stpsaturation",
  785.             "hue",
  786.             "stphue",
  787.             "tint",
  788.             "stptint",
  789.             "contrast",
  790.             "stpcontrast",
  791.             "black",
  792.             "stpblack",
  793.             "cyan",
  794.             "stpcyan",
  795.             "magenta",
  796.             "stpmagenta",
  797.             "yellow",
  798.             "stpyellow",
  799.             "red",
  800.             "stpred",
  801.             "green",
  802.             "stpgreen",
  803.             "blue",
  804.             "stpblue"
  805.             );
  806.  
  807.     my @standardgroups = (
  808.               "general",
  809.               "media",
  810.               "quality",
  811.               "imag",
  812.               "color",
  813.               "output",
  814.               "finish",
  815.               "stapl",
  816.               "extra",
  817.               "install"
  818.               );
  819.  
  820.     my $compare;
  821.  
  822.     # Argument records
  823.     my $firstarg = $a;
  824.     my $secondarg = $b;
  825.  
  826.     # Bring the two option names into a standard form to compare them
  827.     # in a better way
  828.     my $first = normalizename(lc($firstarg->{'name'}));
  829.     $first =~ s/[\W_]//g;
  830.     my $second = normalizename(lc($secondarg->{'name'}));
  831.     $second =~ s/[\W_]//g;
  832.  
  833.     # group names
  834.     my $firstgr = $firstarg->{'group'};
  835.     my @firstgroup;
  836.     @firstgroup = split("/", $firstgr) if defined($firstgr); 
  837.     my $secondgr = $secondarg->{'group'};
  838.     my @secondgroup;
  839.     @secondgroup = split("/", $secondgr) if defined($secondgr);
  840.  
  841.     my $i = 0;
  842.  
  843.     # Compare groups
  844.     while ($firstgroup[$i] && $secondgroup[$i]) {
  845.  
  846.     # Normalize group names
  847.     my $firstgr = normalizename(lc($firstgroup[$i]));
  848.     $firstgr =~ s/[\W_]//g;
  849.     my $secondgr = normalizename(lc($secondgroup[$i]));
  850.     $secondgr =~ s/[\W_]//g;
  851.         
  852.     # Are the groups in the list of standard group names?
  853.     my $j;
  854.     for ($j = 0; $j <= $#standardgroups; $j++) {
  855.         my $firstinlist = ($firstgr =~ /^$standardgroups[$j]/);
  856.         my $secondinlist = ($secondgr =~ /^$standardgroups[$j]/);
  857.         if (($firstinlist) && (!$secondinlist)) {return -1};
  858.         if (($secondinlist) && (!$firstinlist)) {return 1};
  859.         if (($firstinlist) && ($secondinlist)) {last};
  860.     }
  861.  
  862.     # Compare normalized group names
  863.     $compare = $firstgr cmp $secondgr;
  864.     if ($compare != 0) {return $compare};
  865.  
  866.     # Compare original group names
  867.     $compare = $firstgroup[$i] cmp $secondgroup[$i];
  868.     if ($compare != 0) {return $compare};
  869.     
  870.     $i++;
  871.     }
  872.  
  873.     # The one with a deeper level in the group tree will come later
  874.     if ($firstgroup[$i]) {return 1};
  875.     if ($secondgroup[$i]) {return -1};
  876.  
  877.     # Sort by order parameter if the order parameters are different
  878.     if (defined($firstarg->{'order'}) && defined($secondarg->{'order'}) &&
  879.     $firstarg->{'order'} != $secondarg->{'order'}) {
  880.     return $firstarg->{'order'} cmp $secondarg->{'order'};
  881.     }
  882.  
  883.     # Check whether the argument names are in the @standardopts list
  884.     for ($i = 0; $i <= $#standardopts; $i++) {
  885.     my $firstinlist = ($first =~ /^$standardopts[$i]/);
  886.     my $secondinlist = ($second =~ /^$standardopts[$i]/);
  887.     if (($firstinlist) && (!$secondinlist)) {return -1};
  888.     if (($secondinlist) && (!$firstinlist)) {return 1};
  889.     if (($firstinlist) && ($secondinlist)) {last};
  890.     }
  891.  
  892.     # None of the search terms in the list, compare the standard-formed
  893.     # strings
  894.     $compare = ( $first cmp $second );
  895.     if ($compare != 0) {return $compare};
  896.  
  897.     # No other criteria fullfilled, compare the original input strings
  898.     return $firstarg->{'name'} cmp $secondarg->{'name'};
  899. }
  900.  
  901. sub sortvals {
  902.  
  903.     # All sorting done case-insensitive and characters which are not a letter
  904.     # or number are taken out!!
  905.  
  906.     # List of typical choice names to appear at first
  907.     # The terms must fit to the beginning of the line, terms which must fit
  908.     # exactly must have '\$' in the end.
  909.     my @standardvals = (
  910.             # Default setting
  911.             "default",
  912.             "printerdefault",
  913.             # "Neutral" setting
  914.             "None\$",
  915.             # Paper sizes
  916.             "letter\$",
  917.             #"legal",
  918.             "a4\$",
  919.             # Paper types
  920.             "plain",
  921.             # Printout Modes
  922.             "draft\$",
  923.             "draft\.gray",
  924.             "draft\.mono",
  925.             "draft\.",
  926.             "draft",
  927.             "normal\$",
  928.             "normal\.gray",
  929.             "normal\.mono",
  930.             "normal\.",
  931.             "normal",
  932.             "high\$",
  933.             "high\.gray",
  934.             "high\.mono",
  935.             "high\.",
  936.             "high",
  937.             "veryhigh\$",
  938.             "veryhigh\.gray",
  939.             "veryhigh\.mono",
  940.             "veryhigh\.",
  941.             "veryhigh",
  942.             "photo\$",
  943.             "photo\.gray",
  944.             "photo\.mono",
  945.             "photo\.",
  946.             "photo",
  947.             # Trays
  948.             "upper",
  949.             "top",
  950.             "middle",
  951.             "mid",
  952.             "lower",
  953.             "bottom",
  954.             "highcapacity",
  955.             "multipurpose",
  956.             "tray",
  957.             );
  958.  
  959.     # Do not waste time if the input strings are equal
  960.     if ($a eq $b) {return 0;}
  961.  
  962.     # Are the two strings numbers? Compare them numerically
  963.     if (($a =~ /^[\d\.]+$/) && ($b =~ /^[\d\.]+$/)) {
  964.     my $compare = ( $a <=> $b );
  965.     if ($compare != 0) {return $compare};
  966.     }
  967.  
  968.     # Bring the two option names into a standard form to compare them
  969.     # in a better way
  970.     my $first = lc($a);
  971.     $first =~ s/[\W_]//g;
  972.     my $second = lc($b);
  973.     $second =~ s/[\W_]//g;
  974.  
  975.     # Check whether they are in the @standardvals list
  976.     for (my $i = 0; $i <= $#standardvals; $i++) {
  977.     my $firstinlist = ($first =~ /^$standardvals[$i]/);
  978.     my $secondinlist = ($second =~ /^$standardvals[$i]/);
  979.     if (($firstinlist) && (!$secondinlist)) {return -1};
  980.     if (($secondinlist) && (!$firstinlist)) {return 1};
  981.     if (($firstinlist) && ($secondinlist)) {last};
  982.     }
  983.     
  984.     # None of the search terms in the list, compare the standard-formed 
  985.     # strings
  986.     my $compare = ( normalizename($first) cmp normalizename($second) );
  987.     if ($compare != 0) {return $compare};
  988.  
  989.     # No other criteria fullfilled, compare the original input strings
  990.     return $a cmp $b;
  991. }
  992.  
  993. # Take driver/pid arguments and generate a Perl data structure for the
  994. # Perl filter scripts. Sort the options and enumerated choices so that
  995. # they get presented more nicely on frontends which do not sort by
  996. # themselves
  997.  
  998. sub getdat ($ $ $) {
  999.     my ($this, $drv, $poid) = @_;
  1000.  
  1001.     my $ppdfile;
  1002.  
  1003.     # Do we have a link to a custom PPD file for this driver in the
  1004.     # printer XML file? Then return the custom PPD
  1005.  
  1006.     my $p = $this->get_printer($poid);
  1007.     if (defined($p->{'drivers'})) {
  1008.     for my $d (@{$p->{'drivers'}}) {
  1009.         next if ($d->{'id'} ne $drv);
  1010.         $ppdfile = $d->{'ppd'} if defined($d->{'ppd'});
  1011.         last;
  1012.     }
  1013.     }
  1014.  
  1015.     # Do we have a PostScript printer and a link to a manufacturer-
  1016.     # supplied PPD file? Then return the manufacturer-supplied PPD
  1017.  
  1018.     if ($drv =~ /^Postscript$/i) {
  1019.     $ppdfile = $p->{'ppdurl'} if defined($p->{'ppdurl'});
  1020.     }
  1021.  
  1022.     # There is a link to a custom PPD, if it is installed on the local
  1023.     # machine, use the custom PPD instead of generating one from the
  1024.     # Foomatic data
  1025.     if ($ppdfile) {
  1026.     $ppdfile =~ s,^http://.*/(PPD/.*)$,$1,;
  1027.     $ppdfile = $libdir . "/db/source/" . $ppdfile;
  1028.     $ppdfile = "${ppdfile}.gz" if (! -r $ppdfile);
  1029.     if (-r $ppdfile) {
  1030.         $this->getdatfromppd($ppdfile);
  1031.         $this->{'dat'}{'ppdfile'} = $ppdfile;
  1032.         return $this->{'dat'};
  1033.     }
  1034.     }
  1035.  
  1036.     # Generate Perl data structure from database
  1037.     my %dat;            # Our purpose in life...
  1038.     my $VAR1;
  1039.     eval (`$bindir/foomatic-combo-xml -d '$drv' -p '$poid' -l '$libdir' | $bindir/foomatic-perl-data -C -l $this->{'language'}`) ||
  1040.     die ("Could not run \"foomatic-combo-xml\"/" .
  1041.          "\"foomatic-perl-data\"!");
  1042.     %dat = %{$VAR1};
  1043.  
  1044.     # Funky one-at-a-time cache thing
  1045.     $this->{'dat'} = \%dat;
  1046.  
  1047.     # We do some additional stuff which is very awkward to implement in C
  1048.     # now, so we do it here
  1049.  
  1050.     # Some clean-up
  1051.     checklongnames($this->{'dat'});
  1052.     sortoptions($this->{'dat'});
  1053.     generalentries($this->{'dat'});
  1054.     if (defined($this->{'dat'}{'shortdescription'})) {
  1055.     $this->{'dat'}{'shortdescription'} =~ s/[\s\n\r]+/ /s;
  1056.     $this->{'dat'}{'shortdescription'} =~ s/^\s+//;
  1057.     $this->{'dat'}{'shortdescription'} =~ s/\s+$//;
  1058.     }
  1059.     return \%dat;
  1060. }
  1061.  
  1062. sub getdatfromppd ($ $) {
  1063.  
  1064.     my ($this, $ppdfile) = @_;
  1065.  
  1066.     my $dat = ppdtoperl($ppdfile);
  1067.     
  1068.     if (!defined($dat)) {
  1069.     die ("Unable to open PPD file \'$ppdfile\'\n");
  1070.     }
  1071.  
  1072.     $this->{'dat'} = $dat;
  1073.  
  1074. }
  1075.  
  1076. sub ppdfromvartoperl ($);
  1077. sub ppdtoperl($);
  1078. sub perltoxml;
  1079.  
  1080. sub ppdtoperl($) {
  1081.  
  1082.     # Build a Perl data structure of the printer/driver options
  1083.  
  1084.     my ($ppdfile) = @_;
  1085.  
  1086.     # Load the PPD file and send it to the parser
  1087.     open PPD, ($ppdfile !~ /\.gz$/i ? "< $ppdfile" : 
  1088.            "$sysdeps->{'gzip'} -cd \'$ppdfile\' |") or return undef;
  1089.     my @ppd = <PPD>;
  1090.     close PPD;
  1091.     return ppdfromvartoperl(\@ppd);
  1092. }
  1093.  
  1094. sub ppdfromvartoperl ($) {
  1095.  
  1096.     my ($ppd) = @_;
  1097.  
  1098.     # Build a data structure for the renderer's command line and the
  1099.     # options
  1100.  
  1101.     my $dat = {};              # data structure for the options
  1102.     my $currentargument = "";  # We are currently reading this argument
  1103.     my $currentgroup = "";     # We are currently in this group/subgroup
  1104.     my @currentgrouptrans;     # Translation/long name for group/subgroup
  1105.     my $isfoomatic = 0;        # Do we have a Foomatic PPD?
  1106.  
  1107.     # If we have an old Foomatic 2.0.x PPD file, read its built-in Perl
  1108.     # data structure into @datablob and the default values in %ppddefaults
  1109.     # Then delete the $dat structure, replace it by the one "eval"ed from
  1110.     # @datablob, and correct the default settings according to the ones of
  1111.     # the main PPD structure
  1112.     my @datablob;
  1113.     
  1114.     $dat->{"encoding"} = "ascii";
  1115.  
  1116.     # search for LanguageEncoding
  1117.     for (my $i = 0; $i < @{$ppd}; $i ++) {
  1118.     $_ = $ppd->[$i];
  1119.     if (m/^\*LanguageEncoding:\s*(\S+)\s*$/) {
  1120.         # "*LanguageEncoding: <encoding>"        
  1121.         $dat->{'encoding'} = $1;
  1122.         if ($dat->{'encoding'} eq 'MacStandard') {
  1123.         $dat->{'encoding'} = 'MacCentralEurRoman'; 
  1124.         } elsif ($dat->{'encoding'} eq 'WindowsANSI') {
  1125.         $dat->{'encoding'} = 'cp1252'; 
  1126.         } elsif ($dat->{'encoding'} eq 'JIS83-RKSJ') {
  1127.         $dat->{'encoding'} = 'shiftjis';
  1128.         }
  1129.         last;
  1130.     }
  1131.     }
  1132.     # decode PPD
  1133.     my $encoding = $dat->{"encoding"};
  1134.     for (my $i = 0; $i < @{$ppd}; $i ++) {
  1135.     $ppd->[$i] = decode($encoding, $ppd->[$i]);
  1136.     }
  1137.  
  1138.     # Parse the PPD file
  1139.     for (my $i = 0; $i < @{$ppd}; $i ++) {
  1140.     $_ = $ppd->[$i];
  1141.     # Foomatic should also work with PPD files downloaded under
  1142.     # Windows.
  1143.     $_ = undossify($_);
  1144.     # Parse keywords
  1145.     if (m!^\*NickName:\s*\"(.*)$!) {
  1146.         # "*ShortNickName: <code>"
  1147.         my $line = $1;
  1148.         # Store the value
  1149.         # Code string can have multiple lines, read all of them
  1150.         my $cmd = "";
  1151.         while ($line !~ m!\"!) {
  1152.         if ($line =~ m!&&$!) {
  1153.             # line continues in next line
  1154.             $cmd .= substr($line, 0, -2);
  1155.         } else {
  1156.             # line ends here
  1157.             $cmd .= "$line\n";
  1158.         }
  1159.         # Read next line
  1160.         $i ++;
  1161.         $line = $ppd->[$i];
  1162.         chomp $line;
  1163.         }
  1164.         $line =~ m!^([^\"]*)\"!;
  1165.         $cmd .= $1;
  1166.         $dat->{'makemodel'} = unhtmlify($cmd);
  1167.         $dat->{'makemodel'} =~ s/^([^,]+),.*$/$1/;
  1168.         # The following fields are only valid for Foomatic PPDs
  1169.         # they will be deleted when it turns out that this PPD
  1170.         # is not a Foomatic PPD.
  1171.         if ($dat->{'makemodel'} =~ /^(\S+)\s+(\S.*)$/) {
  1172.         $dat->{'make'} = $1;
  1173.         $dat->{'model'} = $2;
  1174.         $dat->{'model'} =~ s/\s+Foomatic.*$//i;
  1175.         }
  1176.     } elsif (m!^\*LanguageVersion:\s*(\S+)\s*$!) {
  1177.         # "*LanguageVersion: <language>"
  1178.         $dat->{'language'} = $1;
  1179.     } elsif (m!^\*FoomaticIDs:\s*(\S+)\s+(\S+)\s*$!) {
  1180.         # "*FoomaticIDs: <printer ID> <driver ID>"
  1181.         my $id = $1;
  1182.         my $driver = $2;
  1183.         # Store the values
  1184.         $dat->{'id'} = $id;
  1185.         $dat->{'driver'} = $driver;
  1186.         $isfoomatic = 1;
  1187.     } elsif (m!^\*FoomaticRIPPostPipe:\s*\"(.*)$!) {
  1188.         # "*FoomaticRIPPostPipe: <code>"
  1189.         my $line = $1;
  1190.         # Store the value
  1191.         # Code string can have multiple lines, read all of them
  1192.         my $cmd = "";
  1193.         while ($line !~ m!\"!) {
  1194.         if ($line =~ m!&&$!) {
  1195.             # line continues in next line
  1196.             $cmd .= substr($line, 0, -2);
  1197.         } else {
  1198.             # line ends here
  1199.             $cmd .= "$line\n";
  1200.         }
  1201.         # Read next line
  1202.         $i ++;
  1203.         $line = $ppd->[$i];
  1204.         chomp $line;
  1205.         }
  1206.         $line =~ m!^([^\"]*)\"!;
  1207.         $cmd .= $1;
  1208.         $dat->{'postpipe'} = unhtmlify($cmd);
  1209.     } elsif (m!^\*FoomaticRIPCommandLine:\s*\"(.*)$!) {
  1210.         # "*FoomaticRIPCommandLine: <code>"
  1211.         my $line = $1;
  1212.         # Store the value
  1213.         # Code string can have multiple lines, read all of them
  1214.         my $cmd = "";
  1215.         while ($line !~ m!\"!) {
  1216.         if ($line =~ m!&&$!) {
  1217.             # line continues in next line
  1218.             $cmd .= substr($line, 0, -2);
  1219.         } else {
  1220.             # line ends here
  1221.             $cmd .= "$line\n";
  1222.         }
  1223.         # Read next line
  1224.         $i ++;
  1225.         $line = $ppd->[$i];
  1226.         chomp $line;
  1227.         }
  1228.         $line =~ m!^([^\"]*)\"!;
  1229.         $cmd .= $1;
  1230.         $dat->{'cmd'} = unhtmlify($cmd);
  1231.     } elsif (m!^\*FoomaticRIPCommandLinePDF:\s*\"(.*)$!) {
  1232.         # "*FoomaticRIPCommandLinePDF: <code>"
  1233.         my $line = $1;
  1234.         # Store the value
  1235.         # Code string can have multiple lines, read all of them
  1236.         my $cmd = "";
  1237.         while ($line !~ m!\"!) {
  1238.         if ($line =~ m!&&$!) {
  1239.             # line continues in next line
  1240.             $cmd .= substr($line, 0, -2);
  1241.         } else {
  1242.             # line ends here
  1243.             $cmd .= "$line\n";
  1244.         }
  1245.         # Read next line
  1246.         $i ++;
  1247.         $line = $ppd->[$i];
  1248.         chomp $line;
  1249.         }
  1250.         $line =~ m!^([^\"]*)\"!;
  1251.         $cmd .= $1;
  1252.         $dat->{'cmd_pdf'} = unhtmlify($cmd);
  1253.     } elsif (m!^\*FoomaticRIPNoPageAccounting:\s*(\S+)\s*$!) {
  1254.         # "*FoomaticRIPNoPageAccounting: <boolean value>"
  1255.         my $value = $1;
  1256.         # Store the value
  1257.         if ($value =~ /^True$/i) {
  1258.         $dat->{'drivernopageaccounting'} = 1;
  1259.         } else {
  1260.         delete $dat->{'drivernopageaccounting'};
  1261.         }
  1262.     } elsif (m!^\*CustomPageSize\s+True:\s*\"(.*)$!) {
  1263.         # "*CustomPageSize True: <code>"
  1264.         my $setting = "Custom";
  1265.         my $translation = "Custom Size";
  1266.         my $line = $1;
  1267.         # Make sure that the argument is in the data structure
  1268.         checkarg ($dat, "PageSize");
  1269.         checkarg ($dat, "PageRegion");
  1270.         # "PageSize" and "PageRegion" must be both user-visible as they are
  1271.         # options required by the PPD spec
  1272.         undef $dat->{'args_byname'}{"PageSize"}{'hidden'};
  1273.         undef $dat->{'args_byname'}{"PageRegion"}{'hidden'};
  1274.         # Make sure that the setting is in the data structure
  1275.         checksetting ($dat, "PageSize", $setting);
  1276.         checksetting ($dat, "PageRegion", $setting);
  1277.         $dat->{'args_byname'}{'PageSize'}{'vals_byname'}{$setting}{'comment'} = $translation;
  1278.         $dat->{'args_byname'}{'PageRegion'}{'vals_byname'}{$setting}{'comment'} = $translation;
  1279.         # Store the value
  1280.         # Code string can have multiple lines, read all of them
  1281.         my $code = "";
  1282.         while ($line !~ m!\"!) {
  1283.         if ($line =~ m!&&$!) {
  1284.             # line continues in next line
  1285.             $code .= substr($line, 0, -2);
  1286.         } else {
  1287.             # line ends here
  1288.             $code .= "$line\n";
  1289.         }
  1290.         # Read next line
  1291.         $i ++;
  1292.         $line = $ppd->[$i];
  1293.         chomp $line;
  1294.         }
  1295.         $line =~ m!^([^\"]*)\"!;
  1296.         $code .= $1;
  1297.         if ($code !~ m!^%% FoomaticRIPOptionSetting!m) {
  1298.         $dat->{'args_byname'}{'PageSize'}{'vals_byname'}{$setting}{'driverval'} = $code;
  1299.         $dat->{'args_byname'}{'PageRegion'}{'vals_byname'}{$setting}{'driverval'} = $code;
  1300.         }
  1301.     } elsif (m!^\*Open(Sub|)Group:\s*\*?([^/]+?)(/(.*)|)$!) {
  1302.         # "*Open[Sub]Group: <group>[/<translation>]
  1303.         my $group = $2;
  1304.         chomp($group) if $group;
  1305.         my $grouptrans = $4;
  1306.         chomp($grouptrans) if $grouptrans;
  1307.         if (!$grouptrans) {
  1308.         $grouptrans = longname($group);
  1309.         }
  1310.         if ($currentgroup) {
  1311.         $currentgroup .= "/";
  1312.         }
  1313.         $currentgroup .= $group;
  1314.         push(@currentgrouptrans, 
  1315.          unhexify($grouptrans, $dat->{"encoding"}));
  1316.     } elsif (m!^\*Close(Sub|)Group:\s*\*?([^/]+?)$!) {
  1317.         # "*Close[Sub]Group: <group>"
  1318.         my $group = $2;
  1319.         chomp($group) if $group;
  1320.         $currentgroup =~ s!$group$!!;
  1321.         $currentgroup =~ s!/$!!;
  1322.         pop(@currentgrouptrans);
  1323.     } elsif (m!^\*Close(Sub|)Group\s*$!) {
  1324.         # "*Close[Sub]Group"
  1325.         # NOTE: This expression is not Adobe-conforming
  1326.         $currentgroup =~ s![^/]+$!!;
  1327.         $currentgroup =~ s!/$!!;
  1328.         pop(@currentgrouptrans);
  1329.     } elsif (m!^\*(JCL|)OpenUI\s+\*([^:]+):\s*(\S+)\s*$!) {
  1330.         # "*[JCL]OpenUI *<option>[/<translation>]: <type>"
  1331.         my $argnametrans = $2;
  1332.         my $argtype = $3;
  1333.         my $argname;
  1334.         my $translation = "";
  1335.         if ($argnametrans =~ m!^([^:/\s]+)/([^:]*)$!) {
  1336.         $argname = $1;
  1337.         $translation = $2;
  1338.         } else {
  1339.         $argname = $argnametrans;
  1340.         }
  1341.         # Make sure that the argument is in the data structure
  1342.         checkarg ($dat, $argname);
  1343.         # This option has a non-Foomatic keyword, so this is not
  1344.         # a hidden option
  1345.         undef $dat->{'args_byname'}{$argname}{'hidden'};
  1346.         # Store the values
  1347.         $dat->{'args_byname'}{$argname}{'comment'} = 
  1348.         unhexify($translation, $dat->{"encoding"});
  1349.         $dat->{'args_byname'}{$argname}{'group'} = $currentgroup;
  1350.         @{$dat->{'args_byname'}{$argname}{'grouptrans'}} =
  1351.         @currentgrouptrans;
  1352.         # Set the argument type only if not defined yet, a
  1353.         # definition in "*FoomaticRIPOption" has priority
  1354.         if (!defined($dat->{'args_byname'}{$argname}{'type'})) {
  1355.         if ($argtype eq "PickOne") {
  1356.             $dat->{'args_byname'}{$argname}{'type'} = 'enum';
  1357.         } elsif ($argtype eq "PickMany") {
  1358.             $dat->{'args_byname'}{$argname}{'type'} = 'pickmany';
  1359.         } elsif ($argtype eq "Boolean") {
  1360.             $dat->{'args_byname'}{$argname}{'type'} = 'bool';
  1361.         }
  1362.         }
  1363.         # Mark in which argument we are currently, so that we can find
  1364.         # the entries for the choices
  1365.         $currentargument = $argname;
  1366.     } elsif (m!^\*(JCL|)CloseUI:\s+\*([^:/\s]+)\s*$!) {
  1367.         next if !$currentargument;
  1368.         # "*[JCL]CloseUI *<option>"
  1369.         my $argname = $2;
  1370.         # Unmark the current argument to do not mis-interpret any 
  1371.         # keywords as choices
  1372.         $currentargument = "";
  1373.     } elsif ((m!^\*FoomaticRIPOption ([^/:\s]+):\s*(\S+)\s+(\S+)\s+(\S)\s*$!) ||
  1374.          (m!^\*FoomaticRIPOption ([^/:\s]+):\s*(\S+)\s+(\S+)\s+(\S)\s+(\S+)\s*$!)){
  1375.         # "*FoomaticRIPOption <option>: <type> <style> <spot> [<order>]"
  1376.         # <order> only used for 1-choice enum options
  1377.         my $argname = $1;
  1378.         my $argtype = $2;
  1379.         my $argstyle = $3;
  1380.         my $spot = $4;
  1381.         my $order = $5;
  1382.         # Make sure that the argument is in the data structure
  1383.         checkarg ($dat, $argname);
  1384.         # Store the values
  1385.         $dat->{'args_byname'}{$argname}{'type'} = $argtype;
  1386.         if ($argstyle eq "PS") {
  1387.         $dat->{'args_byname'}{$argname}{'style'} = 'G';
  1388.         } elsif ($argstyle eq "CmdLine") {
  1389.         $dat->{'args_byname'}{$argname}{'style'} = 'C';
  1390.         } elsif ($argstyle eq "JCL") {
  1391.         $dat->{'args_byname'}{$argname}{'style'} = 'J';
  1392.         $dat->{'jcl'} = 1;
  1393.         $dat->{'pjl'} = 1;
  1394.         } elsif ($argstyle eq "Composite") {
  1395.         $dat->{'args_byname'}{$argname}{'style'} = 'X';
  1396.         }
  1397.         $dat->{'args_byname'}{$argname}{'spot'} = $spot;
  1398.         # $order only defined here for 1-choice enum options
  1399.         if ($order) {
  1400.         $dat->{'args_byname'}{$argname}{'order'} = $order;
  1401.         }
  1402.     } elsif (m!^\*FoomaticRIPOptionPrototype\s+([^/:\s]+):\s*\"(.*)$!) {
  1403.         # "*FoomaticRIPOptionPrototype <option>: <code>"
  1404.         # Used for numerical and string options only
  1405.         my $argname = $1;
  1406.         my $line = $2;
  1407.         # Make sure that the argument is in the data structure
  1408.         checkarg ($dat, $argname);
  1409.         # Store the value
  1410.         # Code string can have multiple lines, read all of them
  1411.         my $proto = "";
  1412.         while ($line !~ m!\"!) {
  1413.         if ($line =~ m!&&$!) {
  1414.             # line continues in next line
  1415.             $proto .= substr($line, 0, -2);
  1416.         } else {
  1417.             # line ends here
  1418.             $proto .= "$line\n";
  1419.         }
  1420.         # Read next line
  1421.         $i ++;
  1422.         $line = $ppd->[$i];
  1423.         chomp $line;
  1424.         }
  1425.         $line =~ m!^([^\"]*)\"!;
  1426.         $proto .= $1;
  1427.         $dat->{'args_byname'}{$argname}{'proto'} = unhtmlify($proto);
  1428.     } elsif (m!^\*FoomaticRIPOptionRange\s+([^/:\s]+):\s*(\S+)\s+(\S+)\s*$!) {
  1429.         # "*FoomaticRIPOptionRange <option>: <min> <max>"
  1430.         # Used for numerical options only
  1431.         my $argname = $1;
  1432.         my $min = $2;
  1433.         my $max = $3;
  1434.         # Make sure that the argument is in the data structure
  1435.         checkarg ($dat, $argname);
  1436.         # Store the values
  1437.         $dat->{'args_byname'}{$argname}{'min'} = $min;
  1438.         $dat->{'args_byname'}{$argname}{'max'} = $max;
  1439.     } elsif (m!^\*FoomaticRIPOptionMaxLength\s+([^/:\s]+):\s*(\S+)\s*$!) {
  1440.         # "*FoomaticRIPOptionMaxLength <option>: <length>"
  1441.         # Used for string options only
  1442.         my $argname = $1;
  1443.         my $maxlength = $2;
  1444.         # Make sure that the argument is in the data structure
  1445.         checkarg ($dat, $argname);
  1446.         # Store the value
  1447.         $dat->{'args_byname'}{$argname}{'maxlength'} = $maxlength;
  1448.     } elsif (m!^\*FoomaticRIPOptionAllowedChars\s+([^/:\s]+):\s*\"(.*)$!) {
  1449.         # "*FoomaticRIPOptionAllowedChars <option>: <code>"
  1450.         # Used for string options only
  1451.         my $argname = $1;
  1452.         my $line = $2;
  1453.         # Store the value
  1454.         # Code string can have multiple lines, read all of them
  1455.         my $code = "";
  1456.         while ($line !~ m!\"!) {
  1457.         if ($line =~ m!&&$!) {
  1458.             # line continues in next line
  1459.             $code .= substr($line, 0, -2);
  1460.         } else {
  1461.             # line ends here
  1462.             $code .= "$line\n";
  1463.         }
  1464.         # Read next line
  1465.         $i ++;
  1466.         $line = $ppd->[$i];
  1467.         chomp $line;
  1468.         }
  1469.         $line =~ m!^([^\"]*)\"!;
  1470.         $code .= $1;
  1471.         # Make sure that the argument is in the data structure
  1472.         checkarg ($dat, $argname);
  1473.         # Store the value
  1474.         $dat->{'args_byname'}{$argname}{'allowedchars'} = unhtmlify($code);
  1475.     } elsif (m!^\*FoomaticRIPOptionAllowedRegExp\s+([^/:\s]+):\s*\"(.*)$!) {
  1476.         # "*FoomaticRIPOptionAllowedRegExp <option>: <code>"
  1477.         # Used for string options only
  1478.         my $argname = $1;
  1479.         my $line = $2;
  1480.         # Store the value
  1481.         # Code string can have multiple lines, read all of them
  1482.         my $code = "";
  1483.         while ($line !~ m!\"!) {
  1484.         if ($line =~ m!&&$!) {
  1485.             # line continues in next line
  1486.             $code .= substr($line, 0, -2);
  1487.         } else {
  1488.             # line ends here
  1489.             $code .= "$line\n";
  1490.         }
  1491.         # Read next line
  1492.         $i ++;
  1493.         $line = $ppd->[$i];
  1494.         chomp $line;
  1495.         }
  1496.         $line =~ m!^([^\"]*)\"!;
  1497.         $code .= $1;
  1498.         # Make sure that the argument is in the data structure
  1499.         checkarg ($dat, $argname);
  1500.         # Store the value
  1501.         $dat->{'args_byname'}{$argname}{'allowedregexp'} =
  1502.         unhtmlify($code);
  1503.     } elsif (m!^\*OrderDependency:\s*(\S+)\s+(\S+)\s+\*([^:/\s]+)\s*$!) {
  1504.         next if !$currentargument;
  1505.         # "*OrderDependency: <order> <section> *<option>"
  1506.         my $order = $1;
  1507.         my $section = $2;
  1508.         my $argname = $3;
  1509.         # Make sure that the argument is in the data structure
  1510.         checkarg ($dat, $argname);
  1511.         # This option has a non-Foomatic keyword, so this is not
  1512.         # a hidden option
  1513.         undef $dat->{'args_byname'}{$argname}{'hidden'};
  1514.         # Store the values
  1515.         $dat->{'args_byname'}{$argname}{'order'} = $order;
  1516.         $dat->{'args_byname'}{$argname}{'section'} = $section;
  1517.     } elsif (m!^\*Default([^/:\s]+):\s*([^/:\s]+)\s*$!) {
  1518.         next if !$currentargument;
  1519.         # "*Default<option>: <value>"
  1520.         my $argname = $1;
  1521.         my $default = $2;
  1522.         # Make sure that the argument is in the data structure
  1523.         checkarg ($dat, $argname);
  1524.         # This option has a non-Foomatic keyword, so this is not
  1525.         # a hidden option
  1526.         undef $dat->{'args_byname'}{$argname}{'hidden'};
  1527.         # Store the value
  1528.         $dat->{'args_byname'}{$argname}{'default'} = $default;
  1529.     } elsif (m!^\*FoomaticRIPDefault([^/:\s]+):\s*([^/:\s]+)\s*$!) {
  1530.         # "*FoomaticRIPDefault<option>: <value>"
  1531.         # Used for numerical options only
  1532.         my $argname = $1;
  1533.         my $default = $2;
  1534.         # Make sure that the argument is in the data structure
  1535.         checkarg ($dat, $argname);
  1536.         # Store the value
  1537.         $dat->{'args_byname'}{$argname}{'fdefault'} = $default;
  1538.     } elsif (m!^\*$currentargument\s+([^:]+):\s*\"(.*)$!) {
  1539.         next if !$currentargument;
  1540.         # "*<option> <choice>[/<translation>]: <code>"
  1541.         my $settingtrans = $1;
  1542.         my $line = $2;
  1543.         my $translation = "";
  1544.         my $setting = "";
  1545.         if ($settingtrans =~ m!^([^:/\s]+)/([^:]*)$!) {
  1546.         $setting = $1;
  1547.         $translation = $2;
  1548.         } else {
  1549.         $setting = $settingtrans;
  1550.         }
  1551.         $translation = unhexify($translation, $dat->{"encoding"});
  1552.         # Make sure that the argument is in the data structure
  1553.         checkarg ($dat, $currentargument);
  1554.         # This option has a non-Foomatic keyword, so this is not
  1555.         # a hidden option
  1556.         undef $dat->{'args_byname'}{$currentargument}{'hidden'};
  1557.         # Make sure that the setting is in the data structure (enum
  1558.         # options)
  1559.         my $bool =
  1560.         ($dat->{'args_byname'}{$currentargument}{'type'} eq 'bool');
  1561.         if ($bool) {
  1562.         if (lc($setting) eq "true") {
  1563.             if (!$dat->{'args_byname'}{$currentargument}{'comment'}) {
  1564.             $dat->{'args_byname'}{$currentargument}{'comment'} =
  1565.                 $translation;
  1566.             }
  1567.             $dat->{'args_byname'}{$currentargument}{'comment_true'} =
  1568.             $translation;
  1569.         } else {
  1570.             $dat->{'args_byname'}{$currentargument}{'comment_false'} =
  1571.             $translation;
  1572.         }
  1573.         } else {
  1574.         checksetting ($dat, $currentargument, $setting);
  1575.         $dat->{'args_byname'}{$currentargument}{'vals_byname'}{$setting}{'comment'} = $translation;
  1576.         # Make sure that this argument has a default setting, even
  1577.         # if none is defined in this PPD file
  1578.         if (!defined($dat->{'args_byname'}{$currentargument}{'default'}) ||
  1579.             ($dat->{'args_byname'}{$currentargument}{'default'} eq "")) {
  1580.             $dat->{'args_byname'}{$currentargument}{'default'} = $setting;
  1581.         }
  1582.         }
  1583.         # Store the value
  1584.         # Code string can have multiple lines, read all of them
  1585.         my $code = "";
  1586.         while ($line !~ m!\"!) {
  1587.         if ($line =~ m!&&$!) {
  1588.             # line continues in next line
  1589.             $code .= substr($line, 0, -2);
  1590.         } else {
  1591.             # line ends here
  1592.             $code .= "$line\n";
  1593.         }
  1594.         # Read next line
  1595.         $i ++;
  1596.         $line = $ppd->[$i];
  1597.         chomp $line;
  1598.         }
  1599.         $line =~ m!^([^\"]*)\"!;
  1600.         $code .= $1;
  1601.         if ($code !~ m!^%% FoomaticRIPOptionSetting!) {
  1602.         if ($bool) {
  1603.             if (lc($setting) eq "true") {
  1604.             $dat->{'args_byname'}{$currentargument}{'proto'} =
  1605.                 $code;
  1606.             } else {
  1607.             $dat->{'args_byname'}{$currentargument}{'protof'} =
  1608.                 $code;
  1609.             }
  1610.         } else {
  1611.             $dat->{'args_byname'}{$currentargument}{'vals_byname'}{$setting}{'driverval'} = $code;
  1612.         }
  1613.         }
  1614.     } elsif ((m!^\*FoomaticRIPOptionSetting\s+([^/:=\s]+)=([^/:=\s]+):\s*\"(.*)$!) ||
  1615.          (m!^\*FoomaticRIPOptionSetting\s+([^/:=\s]+):\s*\"(.*)$!)) {
  1616.         # "*FoomaticRIPOptionSetting <option>[=<choice>]: <code>"
  1617.         # For boolean options <choice> is not given
  1618.         my $argname = $1;
  1619.         my $setting = $2;
  1620.         my $line = $3;
  1621.         my $bool = 0;
  1622.         if (!$line) {
  1623.         $line = $setting;
  1624.         $bool = 1;
  1625.         }
  1626.         # Make sure that the argument is in the data structure
  1627.         checkarg ($dat, $argname);
  1628.         # Make sure that the setting is in the data structure (enum
  1629.         # options)
  1630.         if (!$bool) {
  1631.         checksetting ($dat, $argname, $setting);
  1632.         # Make sure that this argument has a default setting, even
  1633.         # if none is defined in this PPD file
  1634.         if (!$dat->{'args_byname'}{$argname}{'default'}) {
  1635.             $dat->{'args_byname'}{$argname}{'default'} = $setting;
  1636.         }
  1637.         }
  1638.         # Store the value
  1639.         # Code string can have multiple lines, read all of them
  1640.         my $code = "";
  1641.         while ($line !~ m!\"!) {
  1642.         if ($line =~ m!&&$!) {
  1643.             # line continues in next line
  1644.             $code .= substr($line, 0, -2);
  1645.         } else {
  1646.             # line ends here
  1647.             $code .= "$line\n";
  1648.         }
  1649.         # Read next line
  1650.         $i ++;
  1651.         $line = $ppd->[$i];
  1652.         chomp $line;
  1653.         }
  1654.         $line =~ m!^([^\"]*)\"!;
  1655.         $code .= $1;
  1656.         if ($bool) {
  1657.         $dat->{'args_byname'}{$argname}{'proto'} = unhtmlify($code);
  1658.         } else {
  1659.         $dat->{'args_byname'}{$argname}{'vals_byname'}{$setting}{'driverval'} = unhtmlify($code);
  1660.         }
  1661.     } elsif (m!^\*JCL(Begin|ToPSInterpreter|End):\s*\"(.*)$!) {
  1662.         # "*JCL(Begin|ToPSInterpreter|End): <code>"
  1663.         # The printer supports PJL/JCL when there is such a line 
  1664.         $dat->{'jcl'} = 1;
  1665.         $dat->{'pjl'} = 1;
  1666.         my $item = $1;
  1667.         my $line = $2;
  1668.         # Store the value
  1669.         # Code string can have multiple lines, read all of them
  1670.         my $code = "";
  1671.         while ($line !~ m!\"!) {
  1672.         if ($line =~ m!&&$!) {
  1673.             # line continues in next line
  1674.             $code .= substr($line, 0, -2);
  1675.         } else {
  1676.             # line ends here
  1677.             $code .= "$line\n";
  1678.         }
  1679.         # Read next line
  1680.         $i ++;
  1681.         $line = $ppd->[$i];
  1682.         chomp $line;
  1683.         }
  1684.         $line =~ m!^([^\"]*)\"!;
  1685.         $code .= $1;
  1686.         $code = unhexify($code, $dat->{"encoding"});
  1687.         if ($item eq 'Begin') {
  1688.         $dat->{'jclbegin'} = $code;
  1689.         } elsif ($item eq 'ToPSInterpreter') {
  1690.         $dat->{'jcltointerpreter'} = $code;
  1691.         } elsif ($item eq 'End') {
  1692.         $dat->{'jclend'} = $code;
  1693.         }
  1694.     } elsif (m!^\*\% COMDATA \#(.*)$!) {
  1695.         # If we have an old Foomatic 2.0.x PPD file, collect its Perl 
  1696.         # data
  1697.         push (@datablob, $1);
  1698.     }
  1699.     }
  1700.  
  1701.     # If we have an old Foomatic 2.0.x PPD file use its Perl data structure
  1702.     if ($#datablob >= 0) {
  1703.     my $VAR1;
  1704.     if (eval join('',@datablob)) {
  1705.         # Overtake default settings from the main structure of the
  1706.         # PPD file
  1707.         for my $arg (@{$dat->{'args'}}) {
  1708.         if ($arg->{'default'}) {
  1709.             $VAR1->{'argsbyname'}{$arg->{'name'}}{'default'} = 
  1710.             $arg->{'default'};
  1711.         }
  1712.         }
  1713.         undef $dat;
  1714.         $dat = $VAR1;
  1715.         $dat->{'jcl'} = $dat->{'pjl'};
  1716.         $isfoomatic = 1;
  1717.     } else {
  1718.         # Perl structure broken
  1719.         warn "\nUnable to evaluate datablob, print jobs may come " .
  1720.         "out incorrectly or not at all.\n\n";
  1721.     }
  1722.     }
  1723.  
  1724.     # Set the defaults for the numerical options, taking into account
  1725.     # the "*FoomaticRIPDefault<option>: <value>" if they apply
  1726.     numericaldefaults($dat);
  1727.  
  1728.     # Some clean-up
  1729.     checklongnames($dat);
  1730.     generalentries($dat);
  1731.  
  1732.     # Remove make and model fields and sort the options if we don't have 
  1733.     # a Foomatic PPD file
  1734.     if (!$isfoomatic) {
  1735.     $dat->{'make'} = undef;
  1736.     $dat->{'model'} = undef;
  1737.     #sortoptions($dat, 1);
  1738.     }
  1739.  
  1740.     return $dat;
  1741. }
  1742.  
  1743. sub perltoxml {
  1744.     my ($this, $dat, $mode) = @_;
  1745.  
  1746.     my $xml = "";
  1747.  
  1748.     $xml .= "<foomatic>\n" if !$mode || ($mode =~ /^c/i); 
  1749.  
  1750.     if (!$mode || ($mode =~ /^[cp]/i)) { 
  1751.     $xml .=
  1752.         "<printer id=\"printer/" . $dat->{'id'} . "\">\n" .
  1753.         " <make>" . $dat->{'make'} . "</make>\n" .
  1754.         " <model>" . $dat->{'model'} . "</model>\n" .
  1755.         " <mechanism>\n" .
  1756.         ($dat->{'type'} ? "  <" . $dat->{'type'} . "/>\n" : ()) .
  1757.         ($dat->{'color'} ? "  <color/>\n" : ()) .
  1758.         ($dat->{'maxxres'} || $dat->{'maxyres'} ?
  1759.          "  <resolution>\n" .
  1760.          "   <dpi>\n" .
  1761.          ($dat->{'maxxres'} ?
  1762.           "    <x>" . $dat->{'maxxres'} . "</x>\n" : ()) .
  1763.          ($dat->{'maxyres'} ?
  1764.           "    <y>" . $dat->{'maxyres'} . "</y>\n" : ()) .
  1765.          "   </dpi>\n" .
  1766.          "  </resolution>\n" : ()) .
  1767.  
  1768.         " <comments><en /></comments>\n" .
  1769.         "</printer>\n\n\n";
  1770.     }
  1771.  
  1772.     if (!$mode || ($mode =~ /^[cd]/i)) { 
  1773.     $xml .=
  1774.         "<driver id=\"driver/" . $dat->{'driver'} . "\">\n" .
  1775.         " <name>" . $dat->{'driver'} . "</name>\n" .
  1776.         " <execution>\n" .
  1777.         "  <filter />\n" .
  1778.         "  <prototype>" . $dat->{'cmd'} . "</prototype>\n" .
  1779.         $dat->{'cmd_pdf'} ? 
  1780.         "  <prototype_pdf>" . $dat->{'cmd_pdf'} . "</prototype_pdf>\n" :
  1781.         "" .
  1782.         " </execution>\n" .
  1783.         "</driver>\n\n";
  1784.     }
  1785.  
  1786.     if (!$mode || ($mode =~ /^c/i)) { 
  1787.     $xml .= "<options>\n";
  1788.  
  1789.     foreach (@{$dat->{'args'}}) {
  1790.         my $type = $_->{'type'};
  1791.         my $optname = $_->{'name'};
  1792.         $xml .= " <option type=\"$type\" " .
  1793.         "id=\"opt/" . $dat->{'driver'} . "-" . $optname . "\">\n";
  1794.         $xml .=
  1795.         "  <arg_longname>\n" .
  1796.         "   <en>" . $_->{'comment'} . "</en>\n" .
  1797.         "  </arg_longname>\n" .
  1798.         "  <arg_shortname>\n" .
  1799.         "   <en>" . $_->{'name'} . "</en>\n" .
  1800.         "  </arg_shortname>\n" .
  1801.         "  <arg_execution>\n";
  1802.         $xml .= "   <arg_group>" . $_->{'group'} . "</arg_group>\n"
  1803.         if $_->{'group'};
  1804.         $xml .= "   <arg_order>" . $_->{'order'} . "</arg_order>\n"
  1805.         if $_->{'order'};
  1806.         $xml .= "   <arg_spot>" . $_->{'spot'} . "</arg_spot>\n"
  1807.         if $_->{'spot'};
  1808.         $xml .= "   <arg_proto>" . $_->{'proto'} . "</arg_proto>\n"
  1809.         if $_->{'proto'};
  1810.         $xml .= "  </arg_execution>\n";
  1811.         
  1812.         if ($type eq 'enum') {
  1813.         $xml .= "  <enum_vals>\n";
  1814.         my $vals_byname = $_->{'vals_byname'};
  1815.         foreach (keys(%{$vals_byname})) {
  1816.             my $val = $vals_byname->{$_};
  1817.             $xml .=
  1818.             "   <enum_val id=\"ev/" . $dat->{'driver'} . "-" .
  1819.             $optname . "-" . $_ . "\">\n";
  1820.             $xml .=
  1821.             "    <ev_longname>\n" .
  1822.             "     <en>" . $val->{'comment'} . "</en>\n" .
  1823.             "    </ev_longname>\n" .
  1824.             "    <ev_shortname>\n" .
  1825.             "     <en>$_</en>\n" .
  1826.             "    </ev_shortname>\n";
  1827.  
  1828.             $xml .=
  1829.             "    <ev_driverval>" .
  1830.             $val->{'driverval'} .
  1831.             "</ev_driverval>\n" if $val->{'driverval'};
  1832.  
  1833.             $xml .= "   </enum_val>\n";
  1834.         }
  1835.         }
  1836.  
  1837.         $xml .= " </option>\n";
  1838.     }
  1839.  
  1840.     $xml .= "</options>\n";
  1841.     $xml .= "</foomatic>\n";
  1842.     }
  1843.     return $xml;
  1844. }
  1845.  
  1846. sub ppdgetdefaults {
  1847.  
  1848.     # Read a PPD and get only the defaults and the postpipe.
  1849.     my ($this, $ppdfile) = @_;
  1850.  
  1851.     # Open the PPD file
  1852.     open PPD, ($ppdfile !~ /\.gz$/i ? "< $ppdfile" : 
  1853.            "$sysdeps->{'gzip'} -cd \'$ppdfile\' |") or 
  1854.            die ("Unable to open PPD file \'$ppdfile\'\n");
  1855.  
  1856.     # We don't read the "COMDATA" lines of old Foomatic 2.0.x PPD files
  1857.     # here, because the defaults in the main PPD structure have priority.
  1858.     while(<PPD>) {
  1859.     # Foomatic should also work with PPD file downloaded under
  1860.     # Windows.
  1861.     $_ = undossify($_);
  1862.     # Parse keywords
  1863.     if (m!^\*FoomaticRIPPostPipe:\s*\"(.*)$!) {
  1864.         # "*FoomaticRIPPostPipe: <code>"
  1865.         my $line = $1;
  1866.         # Store the value
  1867.         # Code string can have multiple lines, read all of them
  1868.         my $cmd = "";
  1869.         while ($line !~ m!\"!) {
  1870.         if ($line =~ m!&&$!) {
  1871.             # line continues in next line
  1872.             $cmd .= substr($line, 0, -2);
  1873.         } else {
  1874.             # line ends here
  1875.             $cmd .= "$line\n";
  1876.         }
  1877.         # Read next line
  1878.         $line = <PPD>;
  1879.         chomp $line;
  1880.         }
  1881.         $line =~ m!^([^\"]*)\"!;
  1882.         $cmd .= $1;
  1883.         $this->{'dat'}{'postpipe'} = unhtmlify($cmd);
  1884.     } elsif (m!^\*Default([^/:\s]+):\s*([^/:\s]+)\s*$!) {
  1885.         # "*Default<option>: <value>"
  1886.         my $argname = $1;
  1887.         my $default = $2;
  1888.         if (defined($this->{'dat'}{'args_byname'}{$argname})) {
  1889.         # Store the value
  1890.         $this->{'dat'}{'args_byname'}{$argname}{'default'} =
  1891.             $default;
  1892.         }
  1893.     } elsif (m!^\*FoomaticRIPDefault([^/:\s]+):\s*([^/:\s]+)\s*$!) {
  1894.         # "*FoomaticRIPDefault<option>: <value>"
  1895.         # Used for numerical options only
  1896.         my $argname = $1;
  1897.         my $default = $2;
  1898.         if (defined($this->{'dat'}{'args_byname'}{$argname})) {
  1899.         # Store the value
  1900.         $this->{'dat'}{'args_byname'}{$argname}{'fdefault'} =
  1901.             $default;
  1902.         }
  1903.     }
  1904.     }
  1905.  
  1906.     close PPD;
  1907.  
  1908.     # Set the defaults for the numerical options, taking into account
  1909.     # the "*FoomaticRIPDefault<option>: <value>" if they apply
  1910.     #  similar to other places in the code
  1911.     numericaldefaults($this->{'dat'}); 
  1912.  
  1913. }
  1914.  
  1915. sub ppdvarsetdefaults {
  1916.  
  1917.     my ($this, @ppdlinesin) = @_;
  1918.  
  1919.     my @ppdlines;
  1920.     my $ppd;
  1921.  
  1922.     for (my $i = 0; $i < @ppdlinesin; $i ++) {
  1923.     my $line = $ppdlinesin[$i];
  1924.     # Remove a postpipe definition if one is there
  1925.     if ($line =~ m!^\*FoomaticRIPPostPipe:\s*\"(.*)$!) {
  1926.         # "*FoomaticRIPPostPipe: <code>"
  1927.         # Code string can have multiple lines, read all of them
  1928.         $line = $1;
  1929.         while ($line !~ m!\"!) {
  1930.         # Read next line
  1931.         $i++;
  1932.         $line = $ppdlinesin[$i];
  1933.         }
  1934.         # We also have to remove the "*End" line
  1935.         $i++;
  1936.         $line = $ppdlinesin[$i];
  1937.         if ($line !~ /^\*End/) {
  1938.         push(@ppdlines, $line);
  1939.         }
  1940.     } else {
  1941.         push(@ppdlines, $line);
  1942.     }
  1943.     }
  1944.     $ppd = join('', @ppdlines);
  1945.     # No option info read yet? Do not try to set deafaults
  1946.     return $ppd if !$this->{'dat'}{'args'};
  1947.  
  1948.     # If the settings for "PageSize" and "PageRegion" are different,
  1949.     # set the one for "PageRegion" to the one for "PageSize".
  1950.     if ($this->{'dat'}{'args_byname'}{'PageSize'}{'default'} ne
  1951.     $this->{'dat'}{'args_byname'}{'PageRegion'}{'default'}) {
  1952.     $this->{'dat'}{'args_byname'}{'PageRegion'}{'default'} =
  1953.         $this->{'dat'}{'args_byname'}{'PageSize'}{'default'}
  1954.     }
  1955.  
  1956.     # Numerical options: Set the "classical" default values
  1957.     # ("*Default<option>: <value>") to the value enumerated in the
  1958.     # list which is closest to the current default value.
  1959.     setnumericaldefaults($this->{'dat'}); 
  1960.  
  1961.     # Set the defaults in the PPD file to the current default
  1962.     # settings in the data structure
  1963.     for my $arg (@{$this->{'dat'}{'args'}}) {
  1964.     if (defined($arg->{'default'})) {
  1965.         my $name = $arg->{'name'};
  1966.         my $def = $arg->{'default'};
  1967.         if ($arg->{'type'} eq 'bool') {
  1968.         if ((lc($def) eq '1')   || (lc($def) eq 'on') || 
  1969.             (lc($def) eq 'yes') || (lc($def) eq 'true')) {
  1970.             $def='True';
  1971.         } elsif ((lc($def) eq '0')  || (lc($def) eq 'off') || 
  1972.              (lc($def) eq 'no') || (lc($def) eq 'false')) {
  1973.             $def='False';
  1974.         }
  1975.         $def = (checkoptionvalue($this->{'dat'}, $name, $def, 1) ?
  1976.             'True' : 'False');
  1977.         } elsif ($arg->{'type'} =~ /^(int|float)$/) {
  1978.         if (defined($arg->{'cdefault'})) {
  1979.             $def = $arg->{'cdefault'};
  1980.             undef $arg->{'cdefault'};
  1981.         }
  1982.         my $fdef = $arg->{'default'};
  1983.         $fdef = checkoptionvalue($this->{'dat'}, $name, $fdef, 1);
  1984.         $ppd =~ s!^(\*FoomaticRIPDefault$name:\s*)([^/:\s\r]*)(\s*\r?)$!$1$fdef$3!m;
  1985.         $def = checkoptionvalue($this->{'dat'}, $name, $def, 1);
  1986.         } elsif ($arg->{'type'} =~ /^(string|password)$/) {
  1987.         $def = checkoptionvalue($this->{'dat'}, $name, $def, 1);
  1988.         # An empty string cannot be an option name in a PPD file,
  1989.         # use "None" in this case, also substitute non-word characters
  1990.         # in the string to get a legal option name
  1991.         my $defcom = $def;
  1992.         my $defstr = $def;
  1993.         if ($def !~ /\S/) {
  1994.             $def = 'None';
  1995.             $defcom = '(None)';
  1996.             $defstr = '';
  1997.         } elsif ($def eq 'None') {
  1998.             $defcom = '(None)';
  1999.             $defstr = '';
  2000.         } else {
  2001.             $def =~ s/\W+/_/g;
  2002.             $def =~ s/^_+|_+$//g;
  2003.             $def = '_' if ($def eq '');
  2004.             $defcom =~ s/:/ /g;
  2005.             $defcom =~ s/^ +| +$//g;
  2006.         }
  2007.         # The default string is not available as an enumerated choice
  2008.         # ...
  2009.         if (($ppd !~ m!^\s*\*$arg->{name}\s+${def}[/:]!m) &&
  2010.             ($ppd !~ m!^\s*\*FoomaticRIPOptionSetting\s+$arg->{name}=${def}:!m)) {
  2011.             # ... build an appropriate PPD entry ...
  2012.             my $sprintfproto = $arg->{'proto'};
  2013.             $sprintfproto =~ s/\%(?!s)/\%\%/g;
  2014.             my $driverval = sprintf($sprintfproto, $defstr);
  2015.             my ($choicedef, $fchoicedef);
  2016.             if ($arg->{'style'} eq 'G') { # PostScript option
  2017.             $choicedef = sprintf("*%s %s/%s: \"%s\"", 
  2018.                          $name, $def, $defcom, $driverval);
  2019.             } else {
  2020.             my $header = sprintf
  2021.                 ("*FoomaticRIPOptionSetting %s=%s", $name, $def);
  2022.             $fchoicedef = ripdirective($header, $driverval); 
  2023.             if ($#{$arg->{'vals'}} >= 0) { # Visible non-PS option
  2024.                 $choicedef =
  2025.                 sprintf("*%s %s/%s: " .
  2026.                     "\"%%%% FoomaticRIPOptionSetting " .
  2027.                     "%s=%s\"", 
  2028.                     $name, $def, $defcom, $name, $def);
  2029.             }
  2030.             }
  2031.             if ($choicedef =~ /\n/s) {
  2032.             $choicedef .= "\n*End";
  2033.             }
  2034.             if ($fchoicedef =~ /\n/s) {
  2035.             $fchoicedef .= "\n*End";
  2036.             }
  2037.             if ($#{$arg->{'vals'}} == 0) {
  2038.             # ... and if there is only one choice, replace the one 
  2039.             # choice
  2040.             $ppd =~ s!^\*$name\s+.*?\".*?\"(\r?\n?\*End)?$!$choicedef!sm;
  2041.             $ppd =~ s!^\*FoomaticRIPOptionSetting\s+$name=.*?\".*?\"(\r?\n?\*End)?$!$fchoicedef!sm;
  2042.             } else {
  2043.             # ... and if there is no choice or more than one
  2044.             # choice, add a new choice for the default
  2045.             my $entrystr = 
  2046.                 ($choicedef ? "\n$choicedef" : "") .
  2047.                 ($fchoicedef ? "\n$fchoicedef" : "");
  2048.             for my $l ("Default$name:.*",
  2049.                    "OrderDependency.*$name",
  2050.                    "FoomaticRIPOptionMaxLength\\s+$name:.*",
  2051.                    "FoomaticRIPOptionPrototype\\s+$name:.*",
  2052.                    "FoomaticRIPOption\\s+$name:.*") {
  2053.                 $ppd =~ s!^(\*$l)$!$1$entrystr!m and last;
  2054.             }
  2055.             }
  2056.         }
  2057.         } else {
  2058.         $def = checkoptionvalue($this->{'dat'}, $name, $def, 0);
  2059.         }
  2060.         $ppd =~ s!^(\*Default$name:\s*)([^/:\s\r]*)(\s*\r?)$!$1$def$3!m
  2061.         if defined($def);
  2062.     }
  2063.     }
  2064.  
  2065.     # Update the postpipe
  2066.     if ($this->{'dat'}{'postpipe'}) {
  2067.     my $header = "*FoomaticRIPPostPipe";
  2068.     my $code = $this->{'dat'}{'postpipe'};
  2069.     my $postpipestr = ripdirective($header, $code) . "\n";
  2070.     if ($postpipestr =~ /\n.*\n/s) {
  2071.         $postpipestr .= "*End\n";
  2072.     }
  2073.     #$ppd =~ s/(\*PPD[^a-zA-Z0-9].*\n)/$1$postpipestr/s;
  2074.     $ppd =~ s/((\r\n|\n\r|\r|\n))/$1$postpipestr/s;
  2075.     }
  2076.     
  2077.     return $ppd;
  2078. }
  2079.  
  2080. sub ppdsetdefaults {
  2081.  
  2082.     my ($this, $ppdfile) = @_;
  2083.     
  2084.     # Load the complete PPD file into memory
  2085.     open PPD, ($ppdfile !~ /\.gz$/i ? "< $ppdfile" : 
  2086.            "$sysdeps->{'gzip'} -cd \'$ppdfile\' |") or
  2087.            die ("Unable to open PPD file \'$ppdfile\'\n");
  2088.     my @ppdlines = <PPD>;
  2089.     close PPD;
  2090.  
  2091.     # Set the defaults
  2092.     my $ppd = $this->ppdvarsetdefaults(@ppdlines);
  2093.     
  2094.     # Write back the modified PPD file
  2095.     open PPD, ($ppdfile !~ /\.gz$/i ? "> $ppdfile" : 
  2096.            "| $sysdeps->{'gzip'} > \'$ppdfile\'") or
  2097.     die ("Unable to open PPD file \'$ppdfile\' for writing\n");
  2098.     print PPD $ppd;
  2099.     close PPD;
  2100.     
  2101. }
  2102.  
  2103. # Some helper functions for reading the PPD file
  2104.  
  2105. sub unhtmlify {
  2106.     # Replace HTML/XML entities by the original characters
  2107.     my $str = $_[0];
  2108.     $str =~ s/\'/\'/g;
  2109.     $str =~ s/\"/\"/g;
  2110.     $str =~ s/\>/\>/g;
  2111.     $str =~ s/\</\</g;
  2112.     $str =~ s/\&/\&/g;
  2113.     return $str;
  2114. }
  2115.  
  2116. sub unhexify {
  2117.     # Replace hex notation for unprintable characters in PPD files
  2118.     # by the actual characters ex: "<0A>" --> chr(hex("0A"))
  2119.     my ($input, $encoding) = @_;
  2120.     my $output = "";
  2121.     my $hexmode = 0;
  2122.     my $hexstring = "";
  2123.     my $encoded = "";
  2124.     for (my $i = 0; $i < length($input); $i ++) {
  2125.     my $c = substr($input, $i, 1);
  2126.     if ($hexmode) {
  2127.         if ($c eq ">") {
  2128.         # End of hex string
  2129.         $encoded = '';
  2130.         for (my $i=0; $i < length($hexstring); $i+=2) {
  2131.             $encoded .= chr(hex(substr($hexstring, $i, 2)));
  2132.         }
  2133.         $output .= decode($encoding, $encoded);
  2134.         $hexmode = 0;
  2135.         } elsif ($c =~ /^[0-9a-fA-F]$/) {
  2136.         # Hexadecimal digit, two of them give a character
  2137.         $hexstring .= $c; 
  2138.         }
  2139.     } else {
  2140.         if ($c eq "<") {
  2141.         # Beginning of hex string
  2142.         $hexmode = 1;
  2143.         $hexstring = "";
  2144.         } else {
  2145.         # Normal character
  2146.         $output .= $c;
  2147.         }
  2148.     }
  2149.     }
  2150.     return $output;
  2151. }
  2152.  
  2153. sub undossify {
  2154.     # Remove "dossy" line ends ("\r\n") from a string
  2155.     my ($str) = @_;
  2156.     $str = "" if( !defined($str) );
  2157.     $str =~ s/\r\n/\n/gs;
  2158.     $str =~ s/\r$//s;
  2159.     return $str;
  2160. }
  2161.  
  2162. sub checkarg {
  2163.     # Check if there is already an argument record $argname in $dat, if not,
  2164.     # create one
  2165.     my ($dat, $argname) = @_;
  2166.     return if defined($dat->{'args_byname'}{$argname});
  2167.     # argument record
  2168.     my $rec;
  2169.     $rec->{'name'} = $argname;
  2170.     # Insert record in 'args' array for browsing all arguments
  2171.     push(@{$dat->{'args'}}, $rec);
  2172.     # 'args_byname' hash for looking up arguments by name
  2173.     $dat->{'args_byname'}{$argname} = $dat->{'args'}[$#{$dat->{'args'}}];
  2174.     # Default execution style is 'G' (PostScript) since all arguments for
  2175.     # which we don't find "*Foomatic..." keywords are usual PostScript
  2176.     # options
  2177.     $dat->{'args_byname'}{$argname}{'style'} = 'G';
  2178.     # Default prototype for code to insert, used by enum options
  2179.     $dat->{'args_byname'}{$argname}{'proto'} = '%s';
  2180.     # Mark option as hidden by default, as options consisting of only Foomatic
  2181.     # keywords are hidden. As soon as the PPD parser finds a non-Foomatic
  2182.     # keyword, it removes this mark
  2183.     $dat->{'args_byname'}{$argname}{'hidden'} = 1;
  2184. }
  2185.  
  2186. sub checksetting {
  2187.     # Check if there is already a choice record $setting in the $argname
  2188.     # argument in $dat, if not, create one
  2189.     my ($dat, $argname, $setting) = @_;
  2190.     return if 
  2191.     defined($dat->{'args_byname'}{$argname}{'vals_byname'}{$setting});
  2192.     # setting record
  2193.     my $rec;
  2194.     $rec->{'value'} = $setting;
  2195.     # Insert record in 'vals' array for browsing all settings
  2196.     push(@{$dat->{'args_byname'}{$argname}{'vals'}}, $rec);
  2197.     # 'vals_byname' hash for looking up settings by name
  2198.     $dat->{'args_byname'}{$argname}{'vals_byname'}{$setting} = 
  2199.     $dat->{'args_byname'}{$argname}{'vals'}[$#{$dat->{'args_byname'}{$argname}{'vals'}}];
  2200. }
  2201.  
  2202. sub removearg {
  2203.     # remove the argument record $argname from $dat
  2204.     my ($dat, $argname) = @_;
  2205.     return if !defined($dat->{'args_byname'}{$argname});
  2206.     # Remove 'args_byname' hash for looking up arguments by name
  2207.     delete $dat->{'args_byname'}{$argname};
  2208.     # Remove argument itself
  2209.     for (my $i = 0; $i <= $#{$dat->{'args'}}; $i ++) {
  2210.     if ($dat->{'args'}[$i]{'name'} eq $argname) {
  2211.         splice(@{$dat->{'args'}}, $i, 1);
  2212.         last;
  2213.     }
  2214.     }
  2215. }
  2216.  
  2217. sub booltoenum {
  2218.     # Turn the boolean argument $argname from $dat to an enumerated choice
  2219.     # equivalent to the original argument
  2220.     my ($dat, $argname) = @_;
  2221.     return if !defined($dat->{'args_byname'}{$argname});
  2222.     # Argument record
  2223.     my $arg = $dat->{'args_byname'}{$argname};
  2224.     # General settings
  2225.     $arg->{'type'} = 'enum';
  2226.     my $proto = $arg->{'proto'};
  2227.     $arg->{'proto'} = '%s';
  2228.     # Choice for 'true'
  2229.     if (!defined($arg->{'name_true'})) {
  2230.     $arg->{'name_true'} = $arg->{'name'};
  2231.     }
  2232.     checksetting($dat, $argname, 'true');
  2233.     my $truechoice = $arg->{'vals_byname'}{'true'};
  2234.     $truechoice->{'comment'} = longname($arg->{'name_true'});
  2235.     $truechoice->{'driverval'} = $proto;
  2236.     # Choice for 'false'
  2237.     if (!defined($arg->{'name_false'})) {
  2238.     $arg->{'name_false'} = "no$arg->{'name'}";
  2239.     }
  2240.     checksetting($dat, $argname, 'false');
  2241.     my $falsechoice = $arg->{'vals_byname'}{'false'};
  2242.     $falsechoice->{'comment'} = longname($arg->{'name_false'});
  2243.     $falsechoice->{'driverval'} = '';
  2244.     # Default value
  2245.     if ($arg->{'default'} eq '0') {
  2246.     $arg->{'default'} = 'false';
  2247.     } else {
  2248.     $arg->{'default'} = 'true';
  2249.     }
  2250. }
  2251.  
  2252. sub checkoptionvalue {
  2253.  
  2254.     ## This function checks whether a given value is valid for a given
  2255.     ## option. If yes, it returns a cleaned value (e. g. always 0 or 1
  2256.     ## for boolean options), otherwise "undef". If $forcevalue is set,
  2257.     ## we always determine a corrected value to insert (we never return
  2258.     ## "undef").
  2259.  
  2260.     # Is $value valid for the option named $argname?
  2261.     my ($dat, $argname, $value, $forcevalue) = @_;
  2262.  
  2263.     # Record for option $argname
  2264.     my $arg = $dat->{'args_byname'}{$argname};
  2265.  
  2266.     if ($arg->{'type'} eq 'bool') {
  2267.     if ((lc($value) eq 'true') ||
  2268.         (lc($value) eq 'on') ||
  2269.         (lc($value) eq 'yes') ||
  2270.         (lc($value) eq '1')) {
  2271.         return 1;
  2272.     } elsif ((lc($value) eq 'false') ||
  2273.          (lc($value) eq 'off') ||
  2274.          (lc($value) eq 'no') ||
  2275.          (lc($value) eq '0')) {
  2276.         return 0;
  2277.     } elsif ($forcevalue) {
  2278.         # This maps Unknown to mean False.  Good?  Bad?
  2279.         # It was done so in Foomatic 2.0.x, too.
  2280.         return 0;
  2281.     }
  2282.     } elsif ($arg->{'type'} eq 'enum') {
  2283.     if ($arg->{'vals_byname'}{$value}) {
  2284.         return $value;
  2285.     } elsif ((($arg->{'name'} eq "PageSize") ||
  2286.           ($arg->{'name'} eq "PageRegion")) &&
  2287.          (defined($arg->{'vals_byname'}{'Custom'})) &&
  2288.          ($value =~ m!^Custom\.([\d\.]+)x([\d\.]+)([A-Za-z]*)$!)) {
  2289.         # Custom paper size
  2290.         return $value;
  2291.     } elsif ($forcevalue) {
  2292.         # wtf!?  that's not a choice!
  2293.         # Return the first entry of the list
  2294.         my $firstentry = $arg->{'vals'}[0]{'value'};
  2295.         return $firstentry;
  2296.     }
  2297.     } elsif (($arg->{'type'} eq 'int') ||
  2298.          ($arg->{'type'} eq 'float')) {
  2299.     if (($value <= $arg->{'max'}) &&
  2300.         ($value >= $arg->{'min'})) {
  2301.         return $value;
  2302.     } elsif ($forcevalue) {
  2303.         my $newvalue;
  2304.         if ($value > $arg->{'max'}) {
  2305.         $newvalue = $arg->{'max'}
  2306.         } elsif ($value < $arg->{'min'}) {
  2307.         $newvalue = $arg->{'min'}
  2308.         }
  2309.         return $newvalue;
  2310.     }
  2311.     } elsif (($arg->{'type'} eq 'string') ||
  2312.          ($arg->{'type'} eq 'password')) {
  2313.     if (defined($arg->{'vals_byname'}{$value})) {
  2314.         return $value;
  2315.     } elsif (stringvalid($dat, $argname, $value)) {
  2316.         # Check whether the string is one of the enumerated choices
  2317.         my $sprintfproto = $arg->{'proto'};
  2318.         $sprintfproto =~ s/\%(?!s)/\%\%/g;
  2319.         my $driverval = sprintf($sprintfproto, $value);
  2320.         for my $val (@{$arg->{'vals'}}) {
  2321.         if (($val->{'driverval'} eq $driverval) ||
  2322.             ($val->{'driverval'} eq $value)) {
  2323.             return $val->{value};
  2324.         }
  2325.         }
  2326.         # No matching choice? Return the original string
  2327.         return $value;
  2328.     } elsif ($forcevalue) {
  2329.         my $str = substr($value, 0, $arg->{'maxlength'});
  2330.         if (stringvalid($dat, $argname, $str)) {
  2331.         return $str;
  2332.         } elsif ($#{$arg->{'vals'}} >= 0) {
  2333.         # First list item
  2334.         my $firstentry = $arg->{'vals'}[0]{'value'};
  2335.         return $firstentry;
  2336.         } else {
  2337.         # Empty string
  2338.         return 'None';
  2339.         }
  2340.     }
  2341.     }
  2342.     return undef;
  2343. }
  2344.  
  2345. sub stringvalid {
  2346.  
  2347.     ## Checks whether a user-supplied value for a string option is valid
  2348.     ## It must be within the length limit, should only contain allowed
  2349.     ## characters and match the given regexp
  2350.  
  2351.     # Option and string
  2352.     my ($dat, $argname, $value) = @_;
  2353.  
  2354.     my $arg = $dat->{'args_byname'}{$argname};
  2355.  
  2356.     # Maximum length
  2357.     return 0 if (defined($arg->{'maxlength'}) &&
  2358.          (length($value) > $arg->{'maxlength'}));
  2359.  
  2360.     # Allowed characters
  2361.     if ($arg->{'allowedchars'}) {
  2362.     my $chars = $arg->{'allowedchars'};
  2363.     $chars =~ s/(?<!\\)((\\\\)*)\//$2\\\//g;
  2364.     return 0 if $value !~ /^[$chars]*$/;
  2365.     }
  2366.  
  2367.     # Regular expression
  2368.     if ($arg->{'allowedregexp'}) {
  2369.     my $regexp = $arg->{'allowedregexp'};
  2370.     $regexp =~ s/(?<!\\)((\\\\)*)\//$2\\\//g;
  2371.     return 0 if $value !~ /$regexp/;
  2372.     }
  2373.  
  2374.     # All checks passed
  2375.     return 1;
  2376. }
  2377.  
  2378. sub checkoptions {
  2379.  
  2380.     ## Let the values of a boolean option being 0 or 1 instead of
  2381.     ## "True" or "False", range-check the defaults of all options and
  2382.     ## issue warnings if the values are not valid
  2383.  
  2384.     # Option set to be examined
  2385.     my ($dat, $optionset) = @_;
  2386.  
  2387.     for my $arg (@{$dat->{'args'}}) {
  2388.     if (defined($arg->{$optionset})) {
  2389.         $arg->{$optionset} =
  2390.         checkoptionvalue
  2391.         ($dat, $arg->{'name'}, $arg->{$optionset}, 1);
  2392.     }
  2393.     }
  2394.  
  2395.     # If the settings for "PageSize" and "PageRegion" are different,
  2396.     # set the one for "PageRegion" to the one for "PageSize".
  2397.     if ($dat->{'args_byname'}{'PageSize'}{$optionset} ne
  2398.     $dat->{'args_byname'}{'PageRegion'}{$optionset}) {
  2399.     $dat->{'args_byname'}{'PageRegion'}{$optionset} =
  2400.         $dat->{'args_byname'}{'PageSize'}{$optionset};
  2401.     }
  2402. }
  2403.  
  2404. # If the PageSize or PageRegion was changed, also change the other
  2405.  
  2406. sub syncpagesize {
  2407.     
  2408.     # Name and value of the option we set, and the option set where we
  2409.     # did the change
  2410.     my ($dat, $name, $value, $optionset) = @_;
  2411.  
  2412.     # Don't do anything if we were called with an option other than
  2413.     # "PageSize" or "PageRegion"
  2414.     return if (($name ne "PageSize") && ($name ne "PageRegion"));
  2415.     
  2416.     # Don't do anything if not both "PageSize" and "PageRegion" exist
  2417.     return if ((!defined($dat->{'args_byname'}{'PageSize'})) ||
  2418.            (!defined($dat->{'args_byname'}{'PageRegion'})));
  2419.     
  2420.     my $dest;
  2421.     
  2422.     # "PageSize" --> "PageRegion"
  2423.     if ($name eq "PageSize") {
  2424.     $dest = "PageRegion";
  2425.     }
  2426.     
  2427.     # "PageRegion" --> "PageSize"
  2428.     if ($name eq "PageRegion") {
  2429.     $dest = "PageSize";
  2430.     }
  2431.     
  2432.     # Do it!
  2433.     my $val;
  2434.     if ($val=valbyname($dat->{'args_byname'}{$dest}, $value)) {
  2435.     # Standard paper size
  2436.     $dat->{'args_byname'}{$dest}{$optionset} = $val->{'value'};
  2437.     } elsif ($val=valbyname($dat->{'args_byname'}{$dest}, "Custom")) {
  2438.     # Custom paper size
  2439.     $dat->{'args_byname'}{$dest}{$optionset} = $value;
  2440.     }
  2441. }
  2442.  
  2443. sub sortoptions {
  2444.  
  2445.     my ($dat, $only_options) = @_;
  2446.  
  2447.     # The following stuff is very awkward to implement in C, so we do
  2448.     # it here.
  2449.  
  2450.     # Sort options with "sortargs" function
  2451.     my @sortedarglist = sort sortargs @{$dat->{'args'}};
  2452.     @{$dat->{'args'}} = @sortedarglist;
  2453.  
  2454.     return if $only_options;
  2455.  
  2456.     # Sort values of enumerated options with "sortvals" function
  2457.     for my $arg (@{$dat->{'args'}}) {
  2458.     next if $arg->{'type'} !~ /^(enum|string|password)$/;
  2459.            my @sortedvalslist = sort sortvals keys(%{$arg->{'vals_byname'}});
  2460.     @{$arg->{'vals'}} = ();
  2461.     for my $i (@sortedvalslist) {
  2462.         my $val = $arg->{'vals_byname'}{$i};
  2463.         push (@{$arg->{'vals'}}, $val);
  2464.     }
  2465.     }
  2466.  
  2467. }
  2468.  
  2469. sub numericaldefaults {
  2470.  
  2471.     my ($dat) = @_;
  2472.  
  2473.     # Adobe's PPD specs do not support numerical
  2474.     # options. Therefore the numerical options are mapped to
  2475.     # enumerated options in the PPD file and their characteristics
  2476.     # as a numerical option are stored in "*Foomatic..."
  2477.     # keywords. Especially a default value between the enumerated
  2478.     # fixed values can be used as the default value. Then this
  2479.     # value must be given by a "*FoomaticRIPDefault<option>:
  2480.     # <value>" line in the PPD file. But this value is only valid,
  2481.     # if the "official" default given by a "*Default<option>:
  2482.     # <value>" line (it must be one of the enumerated values)
  2483.     # points to the enumerated value which is closest to this
  2484.     # value. This way a user can select a default value with a
  2485.     # tool only supporting PPD files but not Foomatic extensions.
  2486.     # This tool only modifies the "*Default<option>: <value>" line
  2487.     # and if the "*FoomaticRIPDefault<option>: <value>" had always
  2488.     # priority, the user's change in "*Default<option>: <value>"
  2489.     # had no effect.
  2490.  
  2491.     for my $arg (@{$dat->{'args'}}) {
  2492.     if ($arg->{'fdefault'}) {
  2493.         if ($arg->{'default'}) {
  2494.         if ($arg->{'type'} =~ /^(int|float)$/) {
  2495.             if ($arg->{'fdefault'} < $arg->{'min'}) {
  2496.             $arg->{'fdefault'} = $arg->{'min'};
  2497.             }
  2498.             if ($arg->{'fdefault'} > $arg->{'max'}) {
  2499.             $arg->{'fdefault'} = $arg->{'max'};
  2500.             }
  2501.             my $mindiff = abs($arg->{'max'} - $arg->{'min'});
  2502.             my $closestvalue;
  2503.             for my $val (@{$arg->{'vals'}}) {
  2504.             if (abs($arg->{'fdefault'} - $val->{'value'}) <
  2505.                 $mindiff) {
  2506.                 $mindiff = 
  2507.                 abs($arg->{'fdefault'} - $val->{'value'});
  2508.                 $closestvalue = $val->{'value'};
  2509.             }
  2510.             }
  2511.             if (($arg->{'default'} == $closestvalue) ||
  2512.             (abs($arg->{'default'} - $closestvalue) /
  2513.              $closestvalue < 0.001)) {
  2514.             $arg->{'default'} = $arg->{'fdefault'};
  2515.             }
  2516.         }
  2517.         } else {
  2518.         $arg->{'default'} = $arg->{'fdefault'};
  2519.         }
  2520.     }
  2521.     }
  2522. }
  2523.  
  2524. sub setnumericaldefaults {
  2525.  
  2526.     my ($dat) = @_;
  2527.  
  2528.     for my $arg (@{$dat->{'args'}}) {
  2529.     if ($arg->{'default'}) {
  2530.         if ($arg->{'type'} =~ /^(int|float)$/) {
  2531.         if ($arg->{'default'} < $arg->{'min'}) {
  2532.             $arg->{'default'} = $arg->{'min'};
  2533.             $arg->{'cdefault'} = $arg->{'default'};
  2534.         } elsif ($arg->{'default'} > $arg->{'max'}) {
  2535.             $arg->{'default'} = $arg->{'max'};
  2536.             $arg->{'cdefault'} = $arg->{'default'};
  2537.         } elsif (defined($arg->{'vals_byname'}{$arg->{'default'}})) {
  2538.             $arg->{'cdefault'} = $arg->{'default'};
  2539.         } else {
  2540.             my $mindiff = abs($arg->{'max'} - $arg->{'min'});
  2541.             my $closestvalue;
  2542.             for my $val (@{$arg->{'vals'}}) {
  2543.             if (abs($arg->{'default'} - $val->{'value'}) <
  2544.                 $mindiff) {
  2545.                 $mindiff = 
  2546.                 abs($arg->{'default'} - $val->{'value'});
  2547.                 $closestvalue = $val->{'value'};
  2548.             }
  2549.             }
  2550.             $arg->{'cdefault'} = $closestvalue;
  2551.         }
  2552.         }
  2553.     }
  2554.     }
  2555.  
  2556. }
  2557.  
  2558. sub generalentries {
  2559.  
  2560.     my ($dat) = @_;
  2561.  
  2562.     $dat->{'compiled-at'} = localtime(time());
  2563.     $dat->{'timestamp'} = time();
  2564.  
  2565.     my $user = `whoami`; chomp $user;
  2566.     my $host = `hostname`; chomp $host;
  2567.  
  2568.     $dat->{'compiled-by'} = "$user\@$host";
  2569.  
  2570. }
  2571.  
  2572. sub checklongnames {
  2573.  
  2574.     my ($dat) = @_;
  2575.  
  2576.     # Add missing longnames/translations
  2577.     for my $arg (@{$dat->{'args'}}) {
  2578.     if (!($arg->{'comment'})) {
  2579.         $arg->{'comment'} = longname($arg->{'name'});
  2580.     }
  2581.     for my $i (@{$arg->{'vals'}}) {
  2582.         if (!($i->{'comment'})) {
  2583.         $i->{'comment'} = longname($i->{'value'});
  2584.         }
  2585.     }
  2586.     }
  2587. }
  2588.  
  2589. sub cutguiname {
  2590.     
  2591.     # If $shortgui is set and $str is longer than 39 characters, return the
  2592.     # first 39 characters of $str, otherwise the complete $str. 
  2593.  
  2594.     my ($str, $shortgui) = @_;
  2595.  
  2596.     if (($shortgui) && (length($str) > 39)) {
  2597.     return substr($str, 0, 39);
  2598.     } else {
  2599.     return $str;
  2600.     }
  2601. }
  2602.  
  2603. sub deviceIDfromDBEntry {
  2604.  
  2605.     my ($dat) = @_;
  2606.  
  2607.     # Complete IEEE 1284 ID string?
  2608.     my $ieee1284;
  2609.     $ieee1284 = $dat->{'general_ieee'} or $ieee1284 = $dat->{'pnp_ieee'} or
  2610.     $ieee1284 = $dat->{'par_ieee'} or $ieee1284 = $dat->{'usb_ieee'} or 
  2611.     $ieee1284 = $dat->{'snmp_ieee'} or $ieee1284 = "";
  2612.     # Extract data fields from the ID string
  2613.     my $ieeemake;
  2614.     my $ieeemodel;
  2615.     my $ieeecmd;
  2616.     my $ieeedes;
  2617.     if ($ieee1284) {
  2618.     $ieee1284 =~ /(MFG|MANUFACTURER):([^:;]+);/;
  2619.     $ieeemake = $2;
  2620.     $ieee1284 =~ /(MDL|MODEL):([^:;]+);/;
  2621.     $ieeemodel = $2;
  2622.     $ieee1284 =~ /(CMD|COMMANDS\s+SET):([^:;]+);/;
  2623.     $ieeecmd = $2;
  2624.     $ieee1284 =~ /(DES|DESCRIPTION):([^:;]+);/;
  2625.     $ieeedes = $2;
  2626.     }
  2627.     # Auto-detection data listed field by field in the printer XML file?
  2628.     my $pnpmake;
  2629.     $pnpmake = $ieeemake or $pnpmake = $dat->{'general_mfg'} or 
  2630.     $pnpmake = $dat->{'pnp_mfg'} or $pnpmake = $dat->{'par_mfg'} or
  2631.     $pnpmake = $dat->{'usb_mfg'} or $pnpmake = "";
  2632.     my $pnpmodel;
  2633.     $pnpmodel = $ieeemodel or $pnpmodel = $dat->{'general_mdl'} or
  2634.     $pnpmodel = $dat->{'pnp_mdl'} or $pnpmodel = $dat->{'par_mdl'} or
  2635.     $pnpmodel = $dat->{'usb_mdl'} or $pnpmodel = "";
  2636.     my $pnpcmd;
  2637.     $pnpcmd = $ieeecmd or $pnpcmd = $dat->{'general_cmd'} or 
  2638.     $pnpcmd = $dat->{'pnp_cmd'} or $pnpcmd = $dat->{'par_cmd'} or
  2639.     $pnpcmd = $dat->{'usb_cmd'} or $pnpcmd = "";
  2640.     my $pnpdescription;
  2641.     $pnpdescription = $ieeedes or
  2642.     $pnpdescription = $dat->{'general_des'} or
  2643.     $pnpdescription = $dat->{'pnp_des'} or 
  2644.     $pnpdescription = $dat->{'par_des'} or
  2645.     $pnpdescription = $dat->{'usb_des'} or
  2646.     $pnpdescription = "";
  2647.     if ((!$ieee1284) && ((($pnpmake) && ($pnpmodel)) || ($pnpdescription))){
  2648.     $ieee1284 .= "MFG:$pnpmake;" if $pnpmake;
  2649.     $ieee1284 .= "MDL:$pnpmodel;" if $pnpmodel;
  2650.     $ieee1284 .= "CMD:$pnpcmd;" if $pnpcmd;
  2651.     $ieee1284 .= "DES:$pnpdescription;" if $pnpdescription;
  2652.     }
  2653.     return $ieee1284;
  2654. }
  2655.  
  2656. sub ppd1284DeviceID {
  2657.  
  2658.     # Clean up IEEE-1284 device ID to only contain the fields relevant
  2659.     # to printer model auto-detection (MFG, MDL, DES, CMD, SKU), thus
  2660.     # the line length limit of PPDs does not get exceeded on very long
  2661.     # ID strings.
  2662.  
  2663.     my ($id) = @_;
  2664.     my $ppdid = "";
  2665.     
  2666.     foreach my $field ("(MFG|MANUFACTURER)", "(MDL|MODEL)", "(CMD|COMMAND SET)", "(DES|DESCRIPTION)", "SKU", "DRV") {
  2667.     if ($id =~ m/(\b$field:[^:;]+;)/is) {
  2668.         $ppdid .= $1;
  2669.     }
  2670.     }
  2671.  
  2672.     return $ppdid;
  2673. }
  2674.  
  2675. sub getppdheaderdata {
  2676.     
  2677.     my ($dat, $driver, $recdriver) = @_;
  2678.  
  2679.     my $ieee1284 = deviceIDfromDBEntry($dat);
  2680.  
  2681.     # Add driver profile to device ID string, so we get it into the
  2682.     # PPD listing output of CUPS
  2683.     my @profileitems = ();
  2684.     my $profileelements =
  2685.     [["manufacturersupplied", "M"],
  2686.      ["obsolete", "O"],
  2687.      ["free", "F"],
  2688.      ["supportcontacts", "S"],
  2689.      ["type", "T"],
  2690.      ["drvmaxresx", "X"],
  2691.      ["drvmaxresy", "Y"],
  2692.      ["drvcolor", "C"],
  2693.      ["text", "t"],
  2694.      ["lineart", "l"],
  2695.      ["graphics", "g"],
  2696.      ["photo", "p"],
  2697.      ["load", "d"], 
  2698.      ["speed", "s"]];
  2699.     my $drvfield = '';
  2700.     foreach my $item (@{$profileelements}) {
  2701.     my ($perlkey, $devidkey) = @{$item};
  2702.     if ($perlkey eq "manufacturersupplied") {
  2703.         my $ms;
  2704.         if (defined($dat->{$perlkey})) {
  2705.         $ms = $dat->{$perlkey};
  2706.         } elsif (defined($dat->{'driverproperties'}{$driver}{$perlkey})) {
  2707.         $ms = $dat->{'driverproperties'}{$driver}{$perlkey};
  2708.         }
  2709.         $drvfield .= "," . $devidkey .
  2710.         ($ms eq "1" ? "1" : ($dat->{make} =~ m,^($ms)$,i ? "1" : "0"));
  2711.     } elsif ($perlkey eq "supportcontacts") {
  2712.         my $sc;
  2713.         if (defined($dat->{$perlkey})) {
  2714.         $sc = $dat->{$perlkey};
  2715.         } elsif (defined($dat->{'driverproperties'}{$driver}{$perlkey})) {
  2716.         $sc = $dat->{'driverproperties'}{$driver}{$perlkey};
  2717.         }
  2718.         if ($sc) {
  2719.         my $commercial = 0;
  2720.         my $voluntary = 0;
  2721.         my $unknown = 0;
  2722.         foreach my $entry (@{$sc}) {
  2723.             if ($entry->{'level'} =~ /^commercial$/i) {
  2724.             $commercial = 1;
  2725.             } elsif ($entry->{'level'} =~ /^voluntary$/i) {
  2726.             $voluntary = 1;
  2727.             } else {
  2728.             $unknown = 1;
  2729.             }
  2730.         }
  2731.         $drvfield .= "," . $devidkey . ($commercial ? "c" : "") .
  2732.             ($voluntary ? "v" : "") . ($unknown ? "u" : "");
  2733.         }
  2734.     } else {
  2735.         if (defined($dat->{$perlkey})) {
  2736.         $drvfield .= "," . $devidkey . $dat->{$perlkey};
  2737.         } elsif (defined($dat->{'driverproperties'}{$driver}{$perlkey})) {
  2738.         $drvfield .= "," . $devidkey . 
  2739.             $dat->{'driverproperties'}{$driver}{$perlkey};
  2740.         }
  2741.     }
  2742.     }
  2743.     $ieee1284 .= "DRV:D$driver" .
  2744.     ($recdriver ? ($driver eq $recdriver ? ",R1" : ",R0") : "") .
  2745.     "$drvfield;";
  2746.  
  2747.     # Remove everything from the device ID which is not relevant to
  2748.     # auto-detection of the printer model.
  2749.     $ieee1284 = ppd1284DeviceID($ieee1284) if $ieee1284;
  2750.  
  2751.     my $make = $dat->{'make'};
  2752.     my $model = $dat->{'model'};
  2753.  
  2754.     $ieee1284 =~ /(MFG|MANUFACTURER):([^;]+);/;
  2755.     my $pnpmake = $2;
  2756.     $pnpmake = $make if !$pnpmake;
  2757.     $ieee1284 =~ /(MDL|MODEL):([^;]+);/;
  2758.     my $pnpmodel = $2;
  2759.     $pnpmodel = $model if !$pnpmodel;
  2760.  
  2761.     # File name for the PPD file
  2762.     my $filename = join('-',($dat->{'make'},
  2763.                  $dat->{'model'},
  2764.                  $driver));;
  2765.     $filename =~ s![ /\(\)\,]!_!g;
  2766.     $filename =~ s![\+]!plus!g;
  2767.     $filename =~ s!__+!_!g;
  2768.     $filename =~ s!_$!!;
  2769.     $filename =~ s!^_!!;
  2770.     $filename =~ s!_-!-!;
  2771.     $filename =~ s!-_!-!;
  2772.     my $longname = "$filename.ppd";
  2773.  
  2774.     # Driver name
  2775.     my $drivername = $driver;
  2776.  
  2777.     # Do we use the recommended driver?
  2778.     my $driverrecommended = "";
  2779.     if ($driver eq $recdriver) {
  2780.     $driverrecommended = " (recommended)";
  2781.     }
  2782.     
  2783.     # evil special case.
  2784.     $drivername = "stp-4.0" if $drivername eq 'stp';
  2785.  
  2786.     # Nickname for the PPD file
  2787.     my $nickname =
  2788.     "$make $model Foomatic/$drivername$driverrecommended";
  2789.     my $modelname = "$make $model";
  2790.     # Remove forbidden characters (Adobe PPD spec 4.3 section 5.3)
  2791.     $modelname =~ s/[^A-Za-z0-9 \.\/\-\+]//gs;
  2792.  
  2793.     return ($ieee1284,$pnpmake,$pnpmodel,$filename,$longname,
  2794.         $drivername,$nickname,$modelname);
  2795. }
  2796.  
  2797. #
  2798. # PPD generation
  2799. #
  2800.  
  2801. # member( $a, @b ) returns 1 if $a is in @b, 0 otherwise.
  2802. sub member { my $e = shift; foreach (@_) { $e eq $_ and return 1 } 0 };
  2803.  
  2804.  
  2805. sub setgroupandorder {
  2806.  
  2807.     # Set group of member options. Make also sure that the composite
  2808.     # option will be inserted into the PostScript code before all its
  2809.     # # members are inserted (by means of the section and the order #
  2810.     # number).
  2811.  
  2812.     # The composite option to be treated ($arg)
  2813.     my ($db, $arg, $members_in_subgroup) = @_;
  2814.     
  2815.     # The Perl data structure of the current printer/driver combo.
  2816.     my $dat = $db->{'dat'};
  2817.  
  2818.     # Here we are only interested in composite options, skip the others
  2819.     return if $arg->{'style'} ne 'X';
  2820.  
  2821.     my $name = $arg->{'name'};
  2822.     my $group = $arg->{'group'};
  2823.     my $order = $arg->{'order'};
  2824.     my $section = $arg->{'section'};
  2825.     my @members = @{$arg->{'members'}};
  2826.  
  2827.     for my $m (@members) {
  2828.     my $a = $dat->{'args_byname'}{$m};
  2829.  
  2830.     # If $members_in_subgroup is set, the group should be a
  2831.     # subgroup of the group where the composite option is
  2832.     # located, named as the composite option. Otherwise the
  2833.     # group will get a new main group.
  2834.     if (($members_in_subgroup) && ($group)) {
  2835.         $a->{'group'} = "$group/$name";
  2836.     } else {
  2837.         $a->{'group'} = "$name";
  2838.     }
  2839.  
  2840.     # If the member is composite, call this function on it recursively.
  2841.     # This sets the groups of the members of this composite member option
  2842.     # and also sets the section and order number of this composite
  2843.     # member, so that we can so that we can set section and order of the
  2844.     # currently treated option
  2845.     $db->setgroupandorder($a, $members_in_subgroup)
  2846.         if $a->{'style'} eq 'X';
  2847.  
  2848.     # Determine section and order number for the composite option
  2849.     # Order of the DSC sections of a PostScript file
  2850.     my @sectionorder = ("JCLSetup", "Prolog", "DocumentSetup", 
  2851.                 "AnySetup", "PageSetup");
  2852.  
  2853.     # Set default for missing section value in member
  2854.     if (!defined($a->{'section'})) {$a->{'section'} = "AnySetup";}
  2855.     my $minsection;
  2856.     for my $s (@sectionorder) {
  2857.         if (($s eq $arg->{'section'}) || ($s eq $a->{'section'})) {
  2858.         $minsection = $s;
  2859.         last;
  2860.         }
  2861.     }
  2862.  
  2863.     # If the current member option is in an earlier section,
  2864.     # put also the composite option into it. Do never put the
  2865.     # composite option into the JCL setup because in the JCL
  2866.     # header PostScript comments are not allowed.
  2867.     $arg->{'section'} = ($minsection ne "JCLSetup" ?
  2868.                  $minsection : "Prolog");
  2869.  
  2870.     # Let the order number of the composite option be less
  2871.     # than the order number of the current member
  2872.     if ($arg->{'order'} >= $a->{'order'}) {
  2873.         $arg->{'order'} = $a->{'order'} - 1;
  2874.         if ($arg->{'order'} < 0) {
  2875.         $arg->{'order'} = 0;
  2876.         }
  2877.     }
  2878.     }
  2879. }
  2880.  
  2881.  
  2882. # Return a generic Adobe-compliant PPD for the "foomatic-rip" filter script
  2883. # for all spoolers.  Built from the standard data; you must call getdat()
  2884. # first.
  2885. sub getppd (  $ $ $ ) {
  2886.  
  2887.     # If $shortgui is set, all GUI strings ("translations" in PPD
  2888.     # files) will be cut to a maximum length of 39 characters. This is
  2889.     # needed by the current (as of July 2003) version of the CUPS
  2890.     # PostScript driver for Windows.
  2891.  
  2892.     # If $members_in_subgroup is set, the member options of a composite
  2893.     # option go into a subgroup of the group where the composite option
  2894.     # is located. Otherwise the member options go into a new main group
  2895.  
  2896.     my ($db, $shortgui, $members_in_subgroup) = @_;
  2897.  
  2898.     die "you need to call getdat first!\n" 
  2899.     if (!defined($db->{'dat'}));
  2900.  
  2901.     # The Perl data structure of the current printer/driver combo.
  2902.     my $dat = $db->{'dat'};
  2903.  
  2904.     # Do we have a custom pre-made PPD? If so, return this one
  2905.     if (defined($dat->{'ppdfile'})) {
  2906.     my $ppdfile = $dat->{'ppdfile'};
  2907.     $ppdfile = "${ppdfile}.gz" if (! -r $ppdfile);
  2908.     if (-r $ppdfile) {
  2909.         # Load the complete PPD file into memory
  2910.         if (open PPD, ($ppdfile !~ /\.gz$/i ? "< $ppdfile" : 
  2911.                "$sysdeps->{'gzip'} -cd \'$ppdfile\' |")) {
  2912.         my @ppdlines = <PPD>;
  2913.         close PPD;
  2914.         # Set the default values
  2915.         my $ppd = $db->ppdvarsetdefaults(@ppdlines);
  2916.         return $ppd;
  2917.         }
  2918.     }
  2919.     }
  2920.  
  2921.     my @optionblob; # Lines for command line and options in the PPD file
  2922.  
  2923.     # Insert the printer/driver IDs and the command line prototype
  2924.     # right before the option descriptions
  2925.  
  2926.     push(@optionblob, "*FoomaticIDs: $dat->{'id'} $dat->{'driver'}\n");
  2927.     my $header = "*FoomaticRIPCommandLine";
  2928.     my $cmdline = $dat->{'cmd'};
  2929.     my $cmdlinestr = ripdirective($header, $cmdline);
  2930.     if ($cmdline) {
  2931.     # Insert the "*FoomaticRIPCommandLine" directive, but only if
  2932.     # the command line prototype is not empty
  2933.     push(@optionblob, "$cmdlinestr\n");
  2934.     if ($cmdlinestr =~ /\n/s) {
  2935.         push(@optionblob, "*End\n");
  2936.     }
  2937.     }
  2938.     $header = "*FoomaticRIPCommandLinePDF";
  2939.     $cmdline = $dat->{'cmd_pdf'};
  2940.     $cmdlinestr = ripdirective($header, $cmdline);
  2941.     if ($cmdline) {
  2942.     # Insert the "*FoomaticRIPCommandLine" directive, but only if
  2943.     # the command line prototype is not empty
  2944.     push(@optionblob, "$cmdlinestr\n");
  2945.     if ($cmdlinestr =~ /\n/s) {
  2946.         push(@optionblob, "*End\n");
  2947.     }
  2948.     }
  2949.     if ($dat->{'drivernopageaccounting'}) {
  2950.     push(@optionblob, "*FoomaticRIPNoPageAccounting: True\n");
  2951.     }
  2952.  
  2953.     # Search for composite options and prepare the member options
  2954.     # of the found composite options
  2955.     for my $arg (@{$dat->{'args'}}) {
  2956.     # Here we are only interested in composite options, skip the others
  2957.     next if $arg->{'style'} ne 'X';
  2958.     my $name = $arg->{'name'};
  2959.     my $com  = $arg->{'comment'};
  2960.     my $group = $arg->{'group'};
  2961.     my $order = $arg->{'order'};
  2962.     my $section = $arg->{'section'};
  2963.  
  2964.     # The "PageRegion" option is generated automatically, so ignore an
  2965.     # already existing "PageRegion". 
  2966.     next if $name eq "PageRegion";
  2967.  
  2968.     # Set default for missing section value
  2969.     if (!defined($section)) {$arg->{'section'} = "AnySetup";}
  2970.  
  2971.     # Set default for missing tranaslation/longname
  2972.     if (!$com) {$com = longname($name);}
  2973.  
  2974.     my @members;
  2975.  
  2976.     # Go through all choices of the composite option to find its
  2977.     # member options
  2978.     for my $v (@{$arg->{'vals'}}) {
  2979.         my @settings = split(/\s+/s, $v->{'driverval'});
  2980.         for my $s (@settings) {
  2981.         if (($s =~ /^([^=]+)=/) ||
  2982.             ($s =~ /^[Nn][Oo]([^=]+)$/) ||
  2983.             ($s =~ /^([^=]+)$/)) {
  2984.             my $m = $1;
  2985.             # Does the found member exist for this printer/driver
  2986.             # combo?
  2987.             if (defined($dat->{'args_byname'}{$m})) {
  2988.             # Add it to the list of found member options
  2989.             if (!member($m, @members)) {
  2990.                 push(@members, $1);
  2991.             }
  2992.             # Clean up entries for boolean options
  2993.             if ($s !~ /=/) {
  2994.                 if ($s =~ /^[Nn][Oo]$m$/) {
  2995.                 $v->{'driverval'} =~
  2996.                     s/(^|\s)$s($|\s)/$1$m=false$2/;
  2997.                 } else {
  2998.                 $v->{'driverval'} =~ 
  2999.                     s/(^|\s)$s($|\s)/$1$m=true$2/;
  3000.                 }
  3001.             }
  3002.             } else {
  3003.             # Remove it from the choice of the composite
  3004.             # option
  3005.             $v->{'driverval'} =~ s/$s\s*//;
  3006.             $v->{'driverval'} =~ s/\s*$//;
  3007.             }
  3008.         }
  3009.         }
  3010.     }
  3011.  
  3012.     # Add the member list to the data structure of the composite
  3013.     # option. We nned it for the recursive setting of group names
  3014.     # and order numbers
  3015.     $arg->{'members'} = \@members;
  3016.  
  3017.     # Add a "From<Composite>" choice which will be the
  3018.     # default. Check also all members if they are hidden, if so,
  3019.     # this composite option is a forced composite option.
  3020.     my $nothiddenmemberfound = 0;
  3021.     for my $m (@members) {
  3022.         my $a = $dat->{'args_byname'}{$m};
  3023.  
  3024.         # Mark this member as being a member of the current
  3025.         # composite option
  3026.         $a->{'memberof'} = $name;
  3027.  
  3028.         # Convert boolean options to enumerated choice options, so
  3029.         # that we can add the "From<Composite>" choice.
  3030.         if ($a->{'type'} eq 'bool') {
  3031.         booltoenum($dat, $a->{'name'});
  3032.         }
  3033.  
  3034.         # Is this member option hidden?
  3035.         if (!$a->{'hidden'}) {
  3036.         $nothiddenmemberfound = 1;
  3037.         }
  3038.  
  3039.         # In case of a forced composite option mark the member option
  3040.         # as hidden.
  3041.         if (defined($arg->{'substyle'}) &&
  3042.         ($arg->{'substyle'} eq 'F')) {
  3043.         $a->{'hidden'} = 1;
  3044.         }
  3045.  
  3046.         # Do not add a "From<Composite>" choice to an option with only
  3047.         # one choice
  3048.         next if $#{$a->{'vals'}} < 1;
  3049.  
  3050.         if (!defined($a->{'vals_byname'}{"From$name"})) {
  3051.         # Add "From<Composite>" choice
  3052.         # setting record
  3053.         my $rec;
  3054.         $rec->{'value'} = "From$name";
  3055.         $rec->{'comment'} = "Controlled by '$com'";
  3056.         # We mark the driverval as invalid with a non-printable
  3057.         # character, this means that the code to insert will be an
  3058.         # empty string in the PPD.
  3059.         $rec->{'driverval'} = "\x01";
  3060.         # Insert record as the first item in the 'vals' array
  3061.         unshift(@{$a->{'vals'}}, $rec);
  3062.         # Update 'vals_byname' hash
  3063.         $a->{'vals_byname'}{$rec->{'value'}} = $a->{'vals'}[0];
  3064.         for (my $i = 1; $i <= $#{$a->{'vals'}}; $i ++) {
  3065.             $a->{'vals_byname'}{$a->{'vals'}[$i]{'value'}} =
  3066.             $a->{'vals'}[$i];
  3067.         }
  3068.         } else {
  3069.         # Only update the values
  3070.         $a->{'vals_byname'}{"From$name"}{'value'} = "From$name";
  3071.         $a->{'vals_byname'}{"From$name"}{'comment'} =
  3072.             "Controlled by '$com'";
  3073.         $a->{'vals_byname'}{"From$name"}{'driverval'} = "\x01";
  3074.         }
  3075.  
  3076.         # Set default to the new "From<Composite>" choice
  3077.         $a->{'default'} = "From$name";
  3078.     }
  3079.  
  3080.     # If all member options are hidden, this composite option is
  3081.     # a forced composite option and has to be marked appropriately
  3082.     if (!$nothiddenmemberfound) {
  3083.         $arg->{'substyle'} = 'F';
  3084.     }
  3085.     }
  3086.  
  3087.     # Now recursively set the groups and the order sections and numbers
  3088.     # for all composite options and their members.
  3089.     for my $arg (@{$dat->{'args'}}) {
  3090.     # The recursion should only be started in composite options
  3091.     # which are not member of another composite option.
  3092.     $db->setgroupandorder($arg, $members_in_subgroup) 
  3093.         if ($arg->{'style'} eq 'X') and (!$arg->{'memberof'});
  3094.     }
  3095.  
  3096.     # Sort options with "sortargs" function after they were re-grouped
  3097.     # due to the composite options
  3098.     my @sortedarglist = sort sortargs @{$dat->{'args'}};
  3099.     @{$dat->{'args'}} = @sortedarglist;
  3100.  
  3101.     # Construct the option entries for the PPD file
  3102.  
  3103.     my @groupstack; # In which group are we currently
  3104.  
  3105.     for my $arg (@{$dat->{'args'}}) {
  3106.     my $name = $arg->{'name'};
  3107.     my $type = $arg->{'type'};
  3108.     my $com  = $arg->{'comment'};
  3109.     my $default = $arg->{'default'};
  3110.     my $order = $arg->{'order'};
  3111.     my $spot = $arg->{'spot'};
  3112.     my $section = $arg->{'section'};
  3113.     my $cmd = $arg->{'proto'};
  3114.     my @group;
  3115.     @group = split("/", $arg->{'group'}) if defined($arg->{'group'});
  3116.     my $idx = $arg->{'idx'};
  3117.  
  3118.     # What is the execution style of the current option? Skip options
  3119.         # of unknown execution style
  3120.     my $optstyle = ($arg->{'style'} eq 'G' ? "PS" :
  3121.             ($arg->{'style'} eq 'J' ? "JCL" :
  3122.              ($arg->{'style'} eq 'C' ? "CmdLine" :
  3123.               ($arg->{'style'} eq 'X' ? "Composite" :
  3124.                "Unknown"))));
  3125.     next if $optstyle eq "Unknown";
  3126.  
  3127.     # The "PageRegion" option is generated automatically, so ignore an
  3128.     # already existing "PageRegion". 
  3129.     next if $name eq "PageRegion";
  3130.  
  3131.     # The command prototype should not be empty, set default
  3132.     if (!$cmd) {
  3133.         $cmd = "%s";
  3134.     }
  3135.  
  3136.     # Set default for missing section value
  3137.     if (!defined($section)) {$section = "AnySetup";}
  3138.  
  3139.     # Set default for missing tranaslation/longname
  3140.     if (!$com) {$com = longname($name);}
  3141.  
  3142.     # If for a string option the default value is not available under
  3143.     # the enumerated choices, add it here. Make the default choice also
  3144.     # the first list entry
  3145.     if ($type =~ /^(string|password)$/) {
  3146.         $arg->{'default'} =
  3147.         checkoptionvalue($dat, $name, $arg->{'default'}, 1);
  3148.         # An empty string cannot be an option name in a PPD file,
  3149.         # use "None" in this case
  3150.         my $defcom = $arg->{'default'};
  3151.         my $defstr = $arg->{'default'};
  3152.         if ($arg->{'default'} !~ /\S/) {
  3153.         $arg->{'default'} = 'None';
  3154.         $defcom = '(None)';
  3155.         $defstr = '';
  3156.         } elsif ($arg->{'default'} eq 'None') {
  3157.         $defcom = '(None)';
  3158.         $defstr = '';
  3159.         } else {
  3160.         $arg->{'default'} =~ s/\W+/_/g;
  3161.         $arg->{'default'} =~ s/^_+|_+$//g;
  3162.         $arg->{'default'} = '_' if ($arg->{'default'} eq '');
  3163.             $defcom =~ s/:/ /g;
  3164.         $defcom =~ s/^ +| +$//g;
  3165.         }
  3166.         $default = $arg->{'default'};
  3167.         # Generate a new choice
  3168.         if (!defined($arg->{'vals_byname'}{$arg->{'default'}})) {
  3169.         checksetting($dat, $name, $arg->{'default'});
  3170.         my $newchoice = $arg->{'vals_byname'}{$arg->{'default'}};
  3171.         $newchoice->{'value'} = $arg->{'default'};
  3172.         $newchoice->{'comment'} = $defcom;
  3173.         $newchoice->{'driverval'} = $defstr;
  3174.         }
  3175.         # Bring the default entry to the first position
  3176.         my $index = 0;
  3177.         for (my $i = 0; $i <= $#{$arg->{vals}}; $i ++) {
  3178.         if ($arg->{vals}[$i]{'value'} eq $arg->{'default'}) {
  3179.             $index = $i;
  3180.             last;
  3181.         }
  3182.         }
  3183.         my $def = splice(@{$arg->{vals}}, $index, 1);
  3184.         unshift(@{$arg->{vals}}, $def);
  3185.     }
  3186.  
  3187.     # Do we have to open or close one or more groups here?
  3188.     # No group will be opened more than once, since the options
  3189.     # are sorted to have the members of every group together
  3190.  
  3191.     # Only take into account the groups of options which will be
  3192.     # visible user interface options in the PPD.
  3193.     if ((($type !~ /^(enum|string|password)$/) ||
  3194.          ($#{$arg->{'vals'}} > 0) || ($name eq "PageSize") ||
  3195.          ($arg->{'style'} eq 'G')) &&
  3196.         (!$arg->{'hidden'})){
  3197.         # Find the level on which the group path of the current option
  3198.         # (@group) differs from the group path of the last option
  3199.         # (@groupstack).
  3200.         my $level = 0;
  3201.         while (($level <= $#groupstack) and
  3202.            ($level <= $#group) and 
  3203.            ($groupstack[$level] eq $group[$level])) {
  3204.         $level++;
  3205.         }
  3206.         for (my $i = $#groupstack; $i >= $level; $i--) {
  3207.         # Close this group, the current option is not member
  3208.         # of it.
  3209.         push(@optionblob,
  3210.              sprintf("\n*Close%sGroup: %s\n",
  3211.                  ($i > 0 ? "Sub" : ""), $groupstack[$i])
  3212.              );
  3213.         pop(@groupstack);
  3214.         }
  3215.         for (my $i = $level; $i <= $#group; $i++) {
  3216.         # Open this group, the current option is a member
  3217.         # of it.
  3218.         push(@optionblob,
  3219.              sprintf("\n*Open%sGroup: %s/%s\n",
  3220.                  ($i > 0 ? "Sub" : ""), $group[$i], 
  3221.                  cutguiname(longname($group[$i]), $shortgui))
  3222.              );
  3223.         push(@groupstack, $group[$i]);
  3224.         }
  3225.     }
  3226.  
  3227.     if ($type =~ /^(enum|string|password)$/) {
  3228.         # Extra information for string options
  3229.         my ($stringextralines0, $stringextralines1) = ('', '');
  3230.         if ($type =~ /^(string|password)$/) {
  3231.         $stringextralines0 .= sprintf
  3232.              ("*FoomaticRIPOption %s: %s %s %s\n",
  3233.               $name, $type, $optstyle, $spot);
  3234.         my $header = sprintf
  3235.             ("*FoomaticRIPOptionPrototype %s",
  3236.              $name);
  3237.         my $foomaticstr = ripdirective($header, $cmd) . "\n";
  3238.         $stringextralines1 .= $foomaticstr;
  3239.         # Stuff to insert into command line/job is more than one
  3240.         # line? Let an "*End" line follow
  3241.         if ($foomaticstr =~ /\n.*\n/s) {
  3242.             $stringextralines1 .= "*End\n";
  3243.         }
  3244.  
  3245.         if ($arg->{'maxlength'}) {
  3246.             $stringextralines1 .= sprintf
  3247.              ("*FoomaticRIPOptionMaxLength %s: %s\n",
  3248.               $name, $arg->{'maxlength'});
  3249.         }
  3250.  
  3251.         if ($arg->{'allowedchars'}) {
  3252.             my $header = sprintf
  3253.             ("*FoomaticRIPOptionAllowedChars %s",
  3254.              $name);
  3255.             my $entrystr = ripdirective($header, 
  3256.                         $arg->{'allowedchars'}) . "\n";
  3257.             $stringextralines1 .= $entrystr;
  3258.             # Stuff to insert into command line/job is more than one
  3259.             # line? Let an "*End" line follow
  3260.             if ($entrystr =~ /\n.*\n/s) {
  3261.             $stringextralines1 .= "*End\n";
  3262.             }
  3263.         }
  3264.  
  3265.         if ($arg->{'allowedregexp'}) {
  3266.             my $header = sprintf
  3267.             ("*FoomaticRIPOptionAllowedRegExp %s",
  3268.              $name);
  3269.             my $entrystr = ripdirective($header, 
  3270.                         $arg->{'allowedregexp'}) .
  3271.                             "\n";
  3272.             $stringextralines1 .= $entrystr;
  3273.             # Stuff to insert into command line/job is more than one
  3274.             # line? Let an "*End" line follow
  3275.             if ($entrystr =~ /\n.*\n/s) {
  3276.             $stringextralines1 .= "*End\n";
  3277.             }
  3278.         }
  3279.  
  3280.         }
  3281.  
  3282.         # Skip zero or one choice arguments. Do not skip "PageSize",
  3283.         # since a PPD file without "PageSize" will break the CUPS
  3284.         # environment and also do not skip PostScript options. For
  3285.         # skipped options with one choice only "*Foomatic..."
  3286.         # definitions will be used. Skip also the hidden member
  3287.         # options of a forced composite option.
  3288.         if (((1 < scalar(@{$arg->{'vals'}})) ||
  3289.          ($name eq "PageSize") ||
  3290.          ($arg->{'style'} eq 'G')) &&
  3291.         (!$arg->{'hidden'})) {
  3292.  
  3293.         push(@optionblob,
  3294.              sprintf("\n*OpenUI *%s/%s: PickOne\n", $name, 
  3295.                  cutguiname($com, $shortgui)));
  3296.  
  3297.         if ($arg->{'style'} ne 'G') {
  3298.             # For non-PostScript options insert line with option
  3299.             # properties
  3300.             push(@optionblob, sprintf
  3301.              ("*FoomaticRIPOption %s: %s %s %s\n",
  3302.               $name, $type, $optstyle, $spot));
  3303.         }
  3304.  
  3305.         if ($type =~ /^(string|password)$/) {
  3306.             # Extra information for string options
  3307.             push(@optionblob, $stringextralines0, $stringextralines1);
  3308.         }
  3309.  
  3310.         push(@optionblob,
  3311.              sprintf("*OrderDependency: %s %s *%s\n", 
  3312.                  $order, $section, $name),
  3313.              sprintf("*Default%s: %s\n", 
  3314.                  $name,
  3315.                  (defined($default) ? 
  3316.                   checkoptionvalue($dat, $name, $default, 1) :
  3317.                   'Unknown')));
  3318.  
  3319.         if (!defined($default)) {
  3320.             my $whr = sprintf("%s %s driver %s",
  3321.                       $dat->{'make'},
  3322.                       $dat->{'model'},
  3323.                       $dat->{'driver'});
  3324.             warn "undefined default for $idx/$name on a $whr\n";
  3325.         }
  3326.         
  3327.         # If this is the page size argument; construct
  3328.         # PageRegion, ImageableArea, and PaperDimension clauses 
  3329.         # from it. Arguably this is all backwards, but what can
  3330.         # you do! ;)
  3331.         my @pageregion;
  3332.         my @imageablearea;
  3333.         my @paperdimension;
  3334.  
  3335.         # If we have a paper size named "Custom", or one with
  3336.         # one or both dimensions being zero, we must replace
  3337.         # this by an Adobe-complient custom paper size
  3338.         # definition.
  3339.         my $hascustompagesize = 0;
  3340.  
  3341.         # We take very big numbers now, to not impose linits.
  3342.         # Later, when we will have physical demensions of the
  3343.         # printers in the database.
  3344.         my $maxpagewidth = 100000;
  3345.         my $maxpageheight = 100000;
  3346.  
  3347.         # Start the PageRegion, ImageableArea, and PaperDimension
  3348.         # clauses
  3349.         if ($name eq "PageSize") {
  3350.             
  3351.             push(@pageregion,
  3352.              "*OpenUI *PageRegion: PickOne
  3353. *OrderDependency: $order $section *PageRegion
  3354. *DefaultPageRegion: $dat->{'args_byname'}{'PageSize'}{'default'}");
  3355.             push(@imageablearea, 
  3356.              "*DefaultImageableArea: $dat->{'args_byname'}{'PageSize'}{'default'}");
  3357.             push(@paperdimension, 
  3358.              "*DefaultPaperDimension: $dat->{'args_byname'}{'PageSize'}{'default'}");
  3359.         }
  3360.  
  3361.         for my $v (@{$arg->{'vals'}}) {
  3362.             my $psstr = "";
  3363.  
  3364.             if ($name eq "PageSize") {
  3365.             
  3366.             my $value = $v->{'value'}; # in a PPD, the value 
  3367.                                        # is the PPD name...
  3368.             my $comment = $v->{'comment'};
  3369.  
  3370.             # Here we have to fill in the absolute sizes of the 
  3371.             # papers. We consult a table when we could not read
  3372.             # the sizes out of the choices of the "PageSize"
  3373.             # option.
  3374.             my $size = $v->{'driverval'};
  3375.             if ($size =~ /([\d\.]+)x([\d\.]+)([a-z]+)\b/) {
  3376.                 # 2 positive integers separated by 
  3377.                 # an 'x' with a unit
  3378.                 my $w = $1;
  3379.                 my $h = $2;
  3380.                 my $u = $3;
  3381.                 if ($u =~ /^in(|ch(|es))$/i) {
  3382.                 $w *= 72.0;
  3383.                 $h *= 72.0;
  3384.                 } elsif ($u =~ /^mm$/i) {
  3385.                 $w *= 72.0/25.4;
  3386.                 $h *= 72.0/25.4;
  3387.                 } elsif ($u =~ /^cm$/i) {
  3388.                 $w *= 72.0/2.54;
  3389.                 $h *= 72.0/2.54;
  3390.                 }
  3391.                 $w = sprintf("%.2f", $w) if $w =~ /\./;
  3392.                 $h = sprintf("%.2f", $h) if $h =~ /\./;
  3393.                 $size = "$w $h";
  3394.             } elsif (($size =~ /(\d+)[x\s]+(\d+)/) ||
  3395.                 # 2 positive integers separated by 
  3396.                 # whitespace or an 'x'
  3397.                  ($size =~ /\-dDEVICEWIDTHPOINTS\=(\d+)\s+\-dDEVICEHEIGHTPOINTS\=(\d+)/)) {
  3398.                 # "-dDEVICEWIDTHPOINTS=..."/"-dDEVICEHEIGHTPOINTS=..."
  3399.                 $size = "$1 $2";
  3400.             } else {
  3401.                 $size = getpapersize($value);
  3402.             }
  3403.             $size =~ /^\s*([\d\.]+)\s+([\d\.]+)\s*$/;
  3404.             my $width = $1;
  3405.             my $height = $2;
  3406.             if ($maxpagewidth < $width) {
  3407.                 $maxpagewidth = $width;
  3408.             }
  3409.             if ($maxpageheight < $height) {
  3410.                 $maxpageheight = $height;
  3411.             }
  3412.             if (($value eq "Custom") ||
  3413.                 ($width == 0) || ($height == 0)) {
  3414.                 # This page size is either named "Custom" or
  3415.                 # at least one of its dimensions is not fixed
  3416.                 # (=0), so this printer/driver combo must
  3417.                 # support custom page sizes
  3418.                 $hascustompagesize = 1;
  3419.                 # We do not add this size to the PPD file
  3420.                 # because the Adobe standard foresees a
  3421.                 # special code block in the header of the
  3422.                 # PPD file to be inserted when a custom
  3423.                 # page size is requested.
  3424.                 next;
  3425.             }
  3426.             # Determine the unprintable margins
  3427.             # Zero margins when no margin info exists
  3428.             my ($left, $right, $top, $bottom) =
  3429.                 getmargins($dat, $width, $height, $value);
  3430.             # Insert margins in "*ImageableArea" line
  3431.             push(@imageablearea,
  3432.                  "*ImageableArea $value/$comment: " . 
  3433.                  "\"$left $bottom $right $top\"");
  3434.             push(@paperdimension,
  3435.                  "*PaperDimension $value/$comment: \"$size\"");
  3436.             }
  3437.             my $foomaticstr = "";
  3438.             # For PostScript options PostScript code must be 
  3439.             # inserted, unless they are member of a composite
  3440.             # option AND they are set to the "Controlled by
  3441.             # '<Composite>'" choice (driverval is "\x01")
  3442.             if (($arg->{'style'} eq 'G') &&
  3443.             ($v->{'driverval'} ne "\x01")) {
  3444.             # Ghostscript argument; offer up ps for
  3445.             # insertion
  3446.             my $sprintfcmd = $cmd;
  3447.             $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  3448.             $psstr = sprintf($sprintfcmd, 
  3449.                      (defined($v->{'driverval'})
  3450.                       ? $v->{'driverval'}
  3451.                       : $v->{'value'}));
  3452.             } else {
  3453.             # Option setting directive for Foomatic filter
  3454.             # 4 "%" because of the "sprintf" applied to it
  3455.             # In the end stay 2 "%" to have a PostScript 
  3456.             # comment
  3457.             $psstr = sprintf
  3458.                 ("%%%% FoomaticRIPOptionSetting: %s=%s",
  3459.                  $name, $v->{'value'});
  3460.             if ($v->{'driverval'} eq "\x01") {
  3461.                 # Only set the $foomaticstr when the selected
  3462.                 # choice is not the "Controlled by
  3463.                 # '<Composite>'" of a member of a collective
  3464.                 # option. Otherwise leave it out and let
  3465.                 # the value in the "FoomaticRIPOptionSetting"
  3466.                 # comment be "@<Composite>".
  3467.                 $psstr =~ s/=From/=\@/;
  3468.                 $foomaticstr = "";
  3469.             } else {
  3470.                 my $header = sprintf
  3471.                 ("*FoomaticRIPOptionSetting %s=%s",
  3472.                  $name, $v->{'value'});
  3473.                 my $sprintfcmd = $cmd;
  3474.                 $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  3475.                 my $cmdval =
  3476.                 sprintf($sprintfcmd,
  3477.                     (defined($v->{'driverval'})
  3478.                      ? $v->{'driverval'}
  3479.                      : $v->{'value'}));
  3480.                 $foomaticstr = ripdirective($header, $cmdval) . 
  3481.                 "\n";
  3482.             }
  3483.             }
  3484.             # Make sure that the longname/translation exists
  3485.             if (!$v->{'comment'}) {
  3486.             if ($type !~ /^(string|password)$/) {
  3487.                 $v->{'comment'} = longname($v->{'value'});
  3488.             } else {
  3489.                 $v->{'comment'} = $v->{'value'};
  3490.             }
  3491.             }
  3492.             # Code supposed to be inserted into the PostScript
  3493.             # data when this choice is selected.
  3494.             push(@optionblob,
  3495.              sprintf("*%s %s/%s: \"%s\"\n", 
  3496.                  $name, $v->{'value'},
  3497.                  cutguiname($v->{'comment'}, $shortgui),
  3498.                  $psstr));
  3499.             # PostScript code is more than one line? Let an "*End"
  3500.             # line follow
  3501.             if ($psstr =~ /\n/s) {
  3502.             push(@optionblob, "*End\n");
  3503.             }
  3504.             # If we have a command line or JCL option, insert the
  3505.             # code here. For security reasons command line snippets
  3506.             # cannot be inserted into the "official" choice entry,
  3507.             # otherwise the appropriate RIP filter could execute
  3508.             # arbitrary code.
  3509.             push(@optionblob, $foomaticstr);
  3510.             # Stuff to insert into command line/job is more than one
  3511.             # line? Let an "*End" line follow
  3512.             if ($foomaticstr =~ /\n.*\n/s) {
  3513.             push(@optionblob, "*End\n");
  3514.             }
  3515.             # In modern PostScript interpreters "PageRegion" 
  3516.             # and "PageSize" are the same option, so we fill 
  3517.             # in the "PageRegion" the same
  3518.             # way as the "PageSize" choices.
  3519.             if ($name eq "PageSize") {
  3520.             push(@pageregion,
  3521.                  sprintf("*PageRegion %s/%s: \"%s\"", 
  3522.                      $v->{'value'}, $v->{'comment'},
  3523.                      $psstr));
  3524.             if ($psstr =~ /\n/s) {
  3525.                 push(@pageregion, "*End");
  3526.             }
  3527.             }
  3528.         }
  3529.         
  3530.         push(@optionblob,
  3531.              sprintf("*CloseUI: *%s\n", $name));
  3532.  
  3533.         if ($name eq "PageSize") {
  3534.             # Close the PageRegion, ImageableArea, and 
  3535.             # PaperDimension clauses
  3536.             push(@pageregion,
  3537.              "*CloseUI: *PageRegion");
  3538.  
  3539.             my $paperdim = join("\n", 
  3540.                     ("", @pageregion, "", 
  3541.                      @imageablearea, "",
  3542.                      @paperdimension, ""));
  3543.             push (@optionblob, $paperdim);
  3544.  
  3545.             # Make the header entries for a custom page size
  3546.             if ($hascustompagesize) {
  3547.             my $maxpaperdim = 
  3548.                 ($maxpageheight > $maxpagewidth ?
  3549.                  $maxpageheight : $maxpagewidth);
  3550.             # PostScript code from the example 6 in section 6.3
  3551.             # of Adobe's PPD V4.3 specification
  3552.             # http://partners.adobe.com/asn/developer/pdfs/tn/5003.PPD_Spec_v4.3.pdf
  3553.             # If the page size is an option for the command line
  3554.             # of GhostScript, let the values which where put
  3555.             # on the stack being popped and inserta comment
  3556.             # to advise the filter
  3557.             
  3558.             my $pscode;
  3559.             my $foomaticstr = "";
  3560.             if ($arg->{'style'} eq 'G') {
  3561.                 $pscode = "pop pop pop
  3562. <</PageSize [ 5 -2 roll ] /ImagingBBox null>>setpagedevice";
  3563.             } else {
  3564.                 my $a = $arg->{'vals_byname'}{'Custom'};
  3565.                 my $header = sprintf
  3566.                 ("*FoomaticRIPOptionSetting %s=%s",
  3567.                  $name, $a->{'value'});
  3568.                 my $sprintfcmd = $cmd;
  3569.                 $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  3570.                 my $cmdval =
  3571.                 sprintf($sprintfcmd,
  3572.                     (defined($a->{'driverval'})
  3573.                      ? $a->{'driverval'}
  3574.                      : $a->{'value'}));
  3575.                 $foomaticstr =
  3576.                 ripdirective($header, $cmdval) . "\n";
  3577.                 # Stuff to insert into command line/job is more
  3578.                 # than one line? Let an "*End" line follow
  3579.                 if ($foomaticstr =~ /\n.*\n/s) {
  3580.                 $foomaticstr .= "*End\n";
  3581.                 }
  3582.                 $pscode = "pop pop pop pop pop
  3583. %% FoomaticRIPOptionSetting: $name=Custom";
  3584.             }
  3585.             my ($left, $right, $top, $bottom) =
  3586.                 getmargins($dat, 0, 0, 'Custom');
  3587.             my $custompagesizeheader = 
  3588. "*HWMargins: $left $bottom $right $top
  3589. *VariablePaperSize: True
  3590. *MaxMediaWidth: $maxpaperdim
  3591. *MaxMediaHeight: $maxpaperdim
  3592. *NonUIOrderDependency: $order $section *CustomPageSize
  3593. *CustomPageSize True: \"$pscode\"
  3594. *End
  3595. ${foomaticstr}*ParamCustomPageSize Width: 1 points 36 $maxpagewidth
  3596. *ParamCustomPageSize Height: 2 points 36 $maxpageheight
  3597. *ParamCustomPageSize Orientation: 3 int 0 0
  3598. *ParamCustomPageSize WidthOffset: 4 points 0 0
  3599. *ParamCustomPageSize HeightOffset: 5 points 0 0
  3600.  
  3601. ";
  3602.             
  3603.             unshift (@optionblob, $custompagesizeheader);
  3604.             } else {
  3605.             unshift (@optionblob,
  3606.                  "*VariablePaperSize: False\n\n");
  3607.             }
  3608.         }
  3609.         } elsif (((1 == scalar(@{$arg->{'vals'}})) &&
  3610.               ($arg->{'style'} ne 'G')) ||
  3611.              ($arg->{'hidden'})) {
  3612.         # non-PostScript enumerated choice option with one single 
  3613.         # choice or hidden member option of forced composite
  3614.         # option
  3615.  
  3616.         # Insert line with option properties
  3617.         my $foomaticstrs = '';
  3618.         for my $v (@{$arg->{'vals'}}) {
  3619.             my $header = sprintf
  3620.             ("*FoomaticRIPOptionSetting %s=%s",
  3621.              $name, $v->{'value'});
  3622.             my $cmdval = '';
  3623.             # For the "From<Composite>" setting the command line
  3624.             # value is not made use of, so leave it blank then.
  3625.             if ($v->{'driverval'} ne "\x01") {
  3626.             my $sprintfcmd = $cmd;
  3627.             $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  3628.             $cmdval =
  3629.                 sprintf($sprintfcmd,
  3630.                     (defined($v->{'driverval'})
  3631.                      ? $v->{'driverval'}
  3632.                      : $v->{'value'}));
  3633.             }
  3634.             my $foomaticstr = ripdirective($header, $cmdval) . "\n";
  3635.             # Stuff to insert into command line/job is more
  3636.             # than one line? Let an "*End" line follow
  3637.             if ($foomaticstr =~ /\n.*\n/s) {
  3638.             $foomaticstr .= "*End\n";
  3639.             }
  3640.             $foomaticstrs .= $foomaticstr;
  3641.         }
  3642.         push(@optionblob, sprintf
  3643.              ("\n*FoomaticRIPOption %s: %s %s %s %s\n",
  3644.               $name, $type, $optstyle, $spot, $order),
  3645.              $stringextralines1, $foomaticstrs);
  3646.         }
  3647.     } elsif ($type eq 'bool') {
  3648.         my $name = $arg->{'name'};
  3649.         my $namef = $arg->{'name_false'};
  3650.         my $defstr = ($default ? 'True' : 'False');
  3651.         if (!defined($default)) { 
  3652.         $defstr = 'Unknown';
  3653.         }
  3654.         my $psstr = "";
  3655.         my $psstrf = "";
  3656.  
  3657.         push(@optionblob,
  3658.          sprintf("\n*OpenUI *%s/%s: Boolean\n", $name, 
  3659.              cutguiname($com, $shortgui)));
  3660.  
  3661.         if ($arg->{'style'} eq 'G') {
  3662.         # Ghostscript argument
  3663.         $psstr = $cmd;
  3664.         } else {
  3665.         # Option setting directive for Foomatic filter
  3666.         # 4 "%" because of the "sprintf" applied to it
  3667.         # In the end stay 2 "%" to have a PostScript comment
  3668.         my $header = sprintf
  3669.             ("%%%% FoomaticRIPOptionSetting: %s", $name);
  3670.         $psstr = "$header=True";
  3671.         $psstrf = "$header=False";
  3672.         $header = sprintf
  3673.             ("*FoomaticRIPOptionSetting %s", $name);
  3674.         my $foomaticstr = ripdirective($header, $cmd) . "\n";
  3675.         # For non-PostScript options insert line with option
  3676.         # properties
  3677.         push(@optionblob, sprintf
  3678.              ("*FoomaticRIPOption %s: bool %s %s\n",
  3679.               $name, $optstyle, $spot).
  3680.              $foomaticstr,
  3681.              ($foomaticstr =~ /\n.*\n/s ? "*End\n" : ""));
  3682.         }
  3683.  
  3684.         push(@optionblob,
  3685.          sprintf("*OrderDependency: %s AnySetup *%s\n", 
  3686.              $order, $name),
  3687.          sprintf("*Default%s: $defstr\n", $name),
  3688.          sprintf("*%s True/%s: \"%s\"\n", $name, 
  3689.              cutguiname($name, $shortgui), $psstr),
  3690.          ($psstr =~ /\n/s ? "*End\n" : ""),
  3691.          sprintf("*%s False/%s: \"%s\"\n", $name,
  3692.              cutguiname($namef, $shortgui), $psstrf),
  3693.          ($psstrf =~ /\n/s ? "*End\n" : ""),
  3694.          sprintf("*CloseUI: *%s\n", $name));
  3695.         
  3696.     } elsif ($type eq 'int') {
  3697.  
  3698.         # Real numerical options do not exist in the Adobe
  3699.         # specification for PPD files. So we map the numerical
  3700.         # options to enumerated options offering the minimum, the
  3701.         # maximum, the default, and some values inbetween to the
  3702.         # user.
  3703.  
  3704.         my $min = $arg->{'min'};
  3705.         my $max = $arg->{'max'};
  3706.         my $second = $min + 1;
  3707.         my $stepsize = 1;
  3708.         if (($max - $min > 100) && ($name ne "Copies")) {
  3709.         # We don't want to have more than 100 values, but when the
  3710.         # difference between min and max is more than 100 we should
  3711.         # have at least 10 steps.
  3712.         my $mindesiredvalues = 10;
  3713.         my $maxdesiredvalues = 100;
  3714.         # Find the order of magnitude of the value range
  3715.         my $rangesize = $max - $min;
  3716.         my $log10 = log(10.0);
  3717.         my $rangeom = POSIX::floor(log($rangesize)/$log10);
  3718.         # Now find the step size
  3719.         my $trialstepsize = 10 ** $rangeom;
  3720.         my $numvalues = 0;
  3721.         while (($numvalues <= $mindesiredvalues) &&
  3722.                ($trialstepsize > 2)) {
  3723.             $trialstepsize /= 10;
  3724.             $numvalues = $rangesize/$trialstepsize;
  3725.         }
  3726.         # Try to find a finer stepping
  3727.         $stepsize = $trialstepsize;
  3728.         $trialstepsize = $stepsize / 2;
  3729.         $numvalues = $rangesize/$trialstepsize;
  3730.         if ($numvalues <= $maxdesiredvalues) {
  3731.             if ($stepsize > 20) { 
  3732.             $trialstepsize = $stepsize / 4;
  3733.             $numvalues = $rangesize/$trialstepsize;
  3734.             }
  3735.             if ($numvalues <= $maxdesiredvalues) {
  3736.             $trialstepsize = $stepsize / 5;
  3737.             $numvalues = $rangesize/$trialstepsize;
  3738.             }
  3739.             if ($numvalues <= $maxdesiredvalues) {
  3740.             $stepsize = $trialstepsize;
  3741.             } else {
  3742.             $stepsize /= 2;
  3743.             }
  3744.         }
  3745.         $numvalues = $rangesize/$stepsize;
  3746.         # We have the step size. Now we must find an appropriate
  3747.         # second value for the value list, so that it contains
  3748.         # the integer multiples of 10, 100, 1000, ...
  3749.         $second = $stepsize * POSIX::ceil($min / $stepsize);
  3750.         if ($second <= $min) {$second += $stepsize};
  3751.         }
  3752.         # Generate the choice list
  3753.         my @choicelist;
  3754.         push (@choicelist, $min);
  3755.         if (($default < $second) && ($default > $min)) {
  3756.         push (@choicelist, $default);
  3757.         }
  3758.         my $item = $second;
  3759.         while ($item < $max) {
  3760.         push (@choicelist, $item);
  3761.         if (($default < $item + $stepsize) && ($default > $item) &&
  3762.             ($default < $max)) {
  3763.             push (@choicelist, $default);
  3764.         }
  3765.         $item += $stepsize;
  3766.         }
  3767.         push (@choicelist, $max);
  3768.  
  3769.             # Add the option
  3770.  
  3771.         # Skip zero or one choice arguments
  3772.         if (1 < scalar(@choicelist)) {
  3773.         push(@optionblob,
  3774.              sprintf("\n*OpenUI *%s/%s: PickOne\n", $name,
  3775.                  cutguiname($com, $shortgui)));
  3776.  
  3777.         # Insert lines with the special properties of a
  3778.         # numerical option. Do this also for PostScript options
  3779.         # because numerical options are not supported by the PPD
  3780.         # file syntax. This way the info about this option being
  3781.         # a numerical one does not get lost
  3782.  
  3783.         push(@optionblob, sprintf
  3784.              ("*FoomaticRIPOption %s: int %s %s\n",
  3785.               $name, $optstyle, $spot));
  3786.  
  3787.         my $header = sprintf
  3788.             ("*FoomaticRIPOptionPrototype %s",
  3789.              $name);
  3790.         my $foomaticstr = ripdirective($header, $cmd) . "\n";
  3791.         push(@optionblob, $foomaticstr);
  3792.         # Stuff to insert into command line/job is more than one
  3793.         # line? Let an "*End" line follow
  3794.         if ($foomaticstr =~ /\n.*\n/s) {
  3795.             push(@optionblob, "*End\n");
  3796.         }
  3797.  
  3798.         push(@optionblob, sprintf
  3799.              ("*FoomaticRIPOptionRange %s: %s %s\n",
  3800.               $name, $arg->{'min'}, $arg->{'max'}));
  3801.  
  3802.         push(@optionblob,
  3803.              sprintf("*OrderDependency: %s AnySetup *%s\n", 
  3804.                  $order, $name),
  3805.              sprintf("*Default%s: %s\n", 
  3806.                  $name,
  3807.                  (defined($default) ? $default : 'Unknown')),
  3808.              sprintf("*FoomaticRIPDefault%s: %s\n", 
  3809.                  $name,
  3810.                  (defined($default) ? $default : 'Unknown')));
  3811.         if (!defined($default)) {
  3812.             my $whr = sprintf("%s %s driver %s",
  3813.                       $dat->{'make'},
  3814.                       $dat->{'model'},
  3815.                       $dat->{'driver'});
  3816.             warn "undefined default for $idx/$name on a $whr\n";
  3817.         }
  3818.         
  3819.         for my $v (@choicelist) {
  3820.             my $psstr = "";
  3821.             
  3822.             if ($arg->{'style'} eq 'G') {
  3823.             # Ghostscript argument; offer up ps for insertion
  3824.             my $sprintfcmd = $cmd;
  3825.             $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  3826.             $psstr = sprintf($sprintfcmd, $v);
  3827.             } else {
  3828.             # Option setting directive for Foomatic filter
  3829.             # 4 "%" because of the "sprintf" applied to it
  3830.             # In the end stay 2 "%" to have a PostScript comment
  3831.             $psstr = sprintf
  3832.                  ("%%%% FoomaticRIPOptionSetting: %s=%s",
  3833.                   $name, $v);
  3834.             }
  3835.             push(@optionblob,
  3836.              sprintf("*%s %s/%s: \"%s\"\n", 
  3837.                  $name, $v, 
  3838.                  cutguiname($v, $shortgui), $psstr));
  3839.             # PostScript code is more than one line? Let an "*End"
  3840.             # line follow
  3841.             if ($psstr =~ /\n/s) {
  3842.             push(@optionblob, "*End\n");
  3843.             }
  3844.         }
  3845.         
  3846.         push(@optionblob,
  3847.              sprintf("*CloseUI: *%s\n", $name));
  3848.         }
  3849.         
  3850.     } elsif ($type eq 'float') {
  3851.         
  3852.         # Real numerical options do not exist in the Adobe
  3853.         # specification for PPD files. So we map the numerical
  3854.         # options to enumerated options offering the minimum, the
  3855.         # maximum, the default, and some values inbetween to the
  3856.         # user.
  3857.  
  3858.         my $min = $arg->{'min'};
  3859.         my $max = $arg->{'max'};
  3860.         # We don't want to have more than 500 values or less than 50
  3861.         # values.
  3862.         my $mindesiredvalues = 10;
  3863.         my $maxdesiredvalues = 100;
  3864.         # Find the order of magnitude of the value range
  3865.         my $rangesize = $max - $min;
  3866.         my $log10 = log(10.0);
  3867.         my $rangeom = POSIX::floor(log($rangesize)/$log10);
  3868.         # Now find the step size
  3869.         my $trialstepsize = 10 ** $rangeom;
  3870.         my $stepom = $rangeom; # Order of magnitude of stepsize,
  3871.                                # needed for determining necessary number
  3872.                                # of digits
  3873.         my $numvalues = 0;
  3874.         while ($numvalues <= $mindesiredvalues) {
  3875.         $trialstepsize /= 10;
  3876.         $stepom -= 1;
  3877.         $numvalues = $rangesize/$trialstepsize;
  3878.         }
  3879.         # Try to find a finer stepping
  3880.         my $stepsize = $trialstepsize;
  3881.         my $stepsizeorig = $stepsize;
  3882.         $trialstepsize = $stepsizeorig / 2;
  3883.         $numvalues = $rangesize/$trialstepsize;
  3884.         if ($numvalues <= $maxdesiredvalues) {
  3885.         $stepsize = $trialstepsize;
  3886.         $trialstepsize = $stepsizeorig / 4;
  3887.         $numvalues = $rangesize/$trialstepsize;
  3888.         if ($numvalues <= $maxdesiredvalues) {
  3889.             $stepsize = $trialstepsize;
  3890.             $trialstepsize = $stepsizeorig / 5;
  3891.             $numvalues = $rangesize/$trialstepsize;
  3892.             if ($numvalues <= $maxdesiredvalues) {
  3893.             $stepsize = $trialstepsize;
  3894.             }
  3895.         }
  3896.         }
  3897.         $numvalues = $rangesize/$stepsize;
  3898.         if ($stepsize < $stepsizeorig * 0.9) {$stepom -= 1;}
  3899.         # Determine number of digits after the decimal point for
  3900.         # formatting the output values.
  3901.         my $digits = 0;
  3902.         if ($stepom < 0) {
  3903.         $digits = - $stepom;
  3904.         }
  3905.         # We have the step size. Now we must find an appropriate
  3906.         # second value for the value list, so that it contains
  3907.         # the integer multiples of 10, 100, 1000, ...
  3908.         my $second = $stepsize * POSIX::ceil($min / $stepsize);
  3909.         if ($second <= $min) {$second += $stepsize};
  3910.         # Generate the choice list
  3911.         my @choicelist;
  3912.         my $choicestr =  sprintf("%.${digits}f", $min);
  3913.         push (@choicelist, $choicestr);
  3914.         if (($default < $second) && ($default > $min)) {
  3915.         $choicestr =  sprintf("%.${digits}f", $default);
  3916.         # Prevent values from entering twice because of rounding
  3917.         # inacuracy
  3918.         if ($choicestr ne $choicelist[$#choicelist]) {
  3919.             push (@choicelist, $choicestr);
  3920.         }
  3921.         }
  3922.         my $item = $second;
  3923.         my $i = 0;
  3924.         while ($item < $max) {
  3925.         $choicestr =  sprintf("%.${digits}f", $item);
  3926.         # Prevent values from entering twice because of rounding
  3927.         # inacuracy
  3928.         if ($choicestr ne $choicelist[$#choicelist]) {
  3929.             push (@choicelist, $choicestr);
  3930.         }
  3931.         if (($default < $item + $stepsize) && ($default > $item) &&
  3932.             ($default < $max)) {
  3933.             $choicestr =  sprintf("%.${digits}f", $default);
  3934.             # Prevent values from entering twice because of rounding
  3935.             # inacuracy
  3936.             if ($choicestr ne $choicelist[$#choicelist]) {
  3937.             push (@choicelist, $choicestr);
  3938.             }
  3939.         }
  3940.         $i += 1;
  3941.         $item = $second + $i * $stepsize;
  3942.         }
  3943.         $choicestr =  sprintf("%.${digits}f", $max);
  3944.         # Prevent values from entering twice because of rounding
  3945.         # inacuracy
  3946.         if ($choicestr ne $choicelist[$#choicelist]) {
  3947.         push (@choicelist, $choicestr);
  3948.         }
  3949.  
  3950.             # Add the option
  3951.  
  3952.         # Skip zero or one choice arguments
  3953.         if (1 < scalar(@choicelist)) {
  3954.         push(@optionblob,
  3955.              sprintf("\n*OpenUI *%s/%s: PickOne\n", $name, 
  3956.                  cutguiname($com, $shortgui)));
  3957.  
  3958.         # Insert lines with the special properties of a
  3959.         # numerical option. Do this also for PostScript options
  3960.         # because numerical options are not supported by the PPD
  3961.         # file syntax. This way the info about this option being
  3962.         # a numerical one does not get lost
  3963.  
  3964.         push(@optionblob, sprintf
  3965.              ("*FoomaticRIPOption %s: float %s %s\n",
  3966.               $name, $optstyle, $spot));
  3967.  
  3968.         my $header = sprintf
  3969.             ("*FoomaticRIPOptionPrototype %s",
  3970.              $name);
  3971.         my $foomaticstr = ripdirective($header, $cmd) . "\n";
  3972.         push(@optionblob, $foomaticstr);
  3973.         # Stuff to insert into command line/job is more than one
  3974.         # line? Let an "*End" line follow
  3975.         if ($foomaticstr =~ /\n.*\n/s) {
  3976.             push(@optionblob, "*End\n");
  3977.         }
  3978.  
  3979.         push(@optionblob, sprintf
  3980.              ("*FoomaticRIPOptionRange %s: %s %s\n",
  3981.               $name, $arg->{'min'}, $arg->{'max'}));
  3982.  
  3983.         push(@optionblob,
  3984.              sprintf("*OrderDependency: %s AnySetup *%s\n", 
  3985.                  $order, $name),
  3986.              sprintf("*Default%s: %s\n", 
  3987.                  $name,
  3988.                  (defined($default) ? 
  3989.                   sprintf("%.${digits}f", $default) : 'Unknown')),
  3990.              sprintf("*FoomaticRIPDefault%s: %s\n", 
  3991.                  $name,
  3992.                  (defined($default) ? 
  3993.                   sprintf("%.${digits}f", $default) : 'Unknown')));
  3994.         if (!defined($default)) {
  3995.             my $whr = sprintf("%s %s driver %s",
  3996.                       $dat->{'make'},
  3997.                       $dat->{'model'},
  3998.                       $dat->{'driver'});
  3999.             warn "undefined default for $idx/$name on a $whr\n";
  4000.         }
  4001.  
  4002.         for my $v (@choicelist) {
  4003.             my $psstr = "";
  4004.             if ($arg->{'style'} eq 'G') {
  4005.             # Ghostscript argument; offer up ps for insertion
  4006.             my $sprintfcmd = $cmd;
  4007.             $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  4008.             $psstr = sprintf($sprintfcmd, $v);
  4009.             } else {
  4010.             # Option setting directive for Foomatic filter
  4011.             # 4 "%" because of the "sprintf" applied to it
  4012.             # In the end stay 2 "%" to have a PostScript comment
  4013.             $psstr = sprintf
  4014.                  ("%%%% FoomaticRIPOptionSetting: %s=%s",
  4015.                   $name, $v);
  4016.             }
  4017.             push(@optionblob,
  4018.              sprintf("*%s %s/%s: \"%s\"\n", 
  4019.                  $name, $v, 
  4020.                  cutguiname($v, $shortgui), $psstr));
  4021.             # PostScript code is more than one line? Let an "*End"
  4022.             # line follow
  4023.             if ($psstr =~ /\n/s) {
  4024.             push(@optionblob, "*End\n");
  4025.             }
  4026.         }
  4027.         
  4028.         push(@optionblob,
  4029.              sprintf("*CloseUI: *%s\n", $name));
  4030.         }
  4031.         }
  4032.     }
  4033.  
  4034.     # Close the option groups which are still open
  4035.     for (my $i = $#groupstack; $i >= 0; $i--) {
  4036.     push(@optionblob,
  4037.          sprintf("\n*Close%sGroup: %s\n",
  4038.              ($i > 0 ? "Sub" : ""), $groupstack[$i])
  4039.          );
  4040.     pop(@groupstack);
  4041.     }
  4042.  
  4043.     if (! $dat->{'args_byname'}{'PageSize'} ) {
  4044.     
  4045.     # This is a problem, since CUPS segfaults on PPD files without
  4046.     # a default PageSize set.  Indeed, the PPD spec requires a
  4047.     # PageSize clause.
  4048.     
  4049.     # GhostScript does not understand "/PageRegion[...]", therefore
  4050.     # we use "/PageSize[...]" in the "*PageRegion" option here, in
  4051.     # addition, for most modern PostScript interpreters "PageRegion"
  4052.     # is the same as "PageSize".
  4053.  
  4054.     push(@optionblob, <<EOFPGSZ);
  4055.  
  4056. *% This is fake. We have no information on how to
  4057. *% set the pagesize for this driver in the database. To
  4058. *% prevent PPD users from blowing up, we must provide a
  4059. *% default pagesize value.
  4060.  
  4061. *OpenUI *PageSize/Media Size: PickOne
  4062. *OrderDependency: 10 AnySetup *PageSize
  4063. *DefaultPageSize: Letter
  4064. *PageSize Letter/Letter: "<</PageSize[612 792]/ImagingBBox null>>setpagedevice"
  4065. *PageSize Legal/Legal: "<</PageSize[612 1008]/ImagingBBox null>>setpagedevice"
  4066. *PageSize A4/A4: "<</PageSize[595 842]/ImagingBBox null>>setpagedevice"
  4067. *CloseUI: *PageSize
  4068.  
  4069. *OpenUI *PageRegion: PickOne
  4070. *OrderDependency: 10 AnySetup *PageRegion
  4071. *DefaultPageRegion: Letter
  4072. *PageRegion Letter/Letter: "<</PageSize[612 792]/ImagingBBox null>>setpagedevice"
  4073. *PageRegion Legal/Legal: "<</PageSize[612 1008]/ImagingBBox null>>setpagedevice"
  4074. *PageRegion A4/A4: "<</PageSize[595 842]/ImagingBBox null>>setpagedevice"
  4075. *CloseUI: *PageRegion
  4076.  
  4077. *DefaultImageableArea: Letter
  4078. *ImageableArea Letter/Letter:    "0 0 612 792"
  4079. *ImageableArea Legal/Legal:    "0 0 612 1008"
  4080. *ImageableArea A4/A4:    "0 0 595 842"
  4081.  
  4082. *DefaultPaperDimension: Letter
  4083. *PaperDimension Letter/Letter:    "612 792"
  4084. *PaperDimension Legal/Legal:    "612 1008"
  4085. *PaperDimension A4/A4:    "595 842"
  4086.  
  4087. EOFPGSZ
  4088.     }
  4089.  
  4090.     my @others;
  4091.  
  4092.     my $headcomment =
  4093. "*% For information on using this, and to obtain the required backend
  4094. *% script, consult http://www.openprinting.org/
  4095. *%
  4096. *% This file is published under the GNU General Public License
  4097. *%
  4098. *% PPD-O-MATIC (3.0.0 or newer) generated this PPD file. It is for use with 
  4099. *% all programs and environments which use PPD files for dealing with
  4100. *% printer capability information. The printer must be configured with the
  4101. *% \"foomatic-rip\" backend filter script of Foomatic 3.0.0 or newer. This 
  4102. *% file and \"foomatic-rip\" work together to support PPD-controlled printer
  4103. *% driver option access with arbitrary free software printer drivers and
  4104. *% printing spoolers.
  4105. *%
  4106. *% To save this file on your disk, wait until the download has completed
  4107. *% (the animation of the browser logo must stop) and then use the
  4108. *% \"Save as...\" command in the \"File\" menu of your browser or in the 
  4109. *% pop-up manu when you click on this document with the right mouse button.
  4110. *% DO NOT cut and paste this file into an editor with your mouse. This can
  4111. *% introduce additional line breaks which lead to unexpected results.";
  4112.  
  4113.     my $postpipe = "";
  4114.     if ($dat->{'postpipe'}) {
  4115.     my $header = "*FoomaticRIPPostPipe";
  4116.     my $code = $dat->{'postpipe'};
  4117.     $postpipe = ripdirective($header, $code) . "\n";
  4118.     if ($postpipe =~ /\n.*\n/s) {
  4119.         $postpipe .= "*End\n";
  4120.     }
  4121.     }
  4122.     my $opts = join('',@optionblob);
  4123.     my $otherstuff = join('',@others);
  4124.     my $pcfilename;
  4125.     if (($dat->{'pcmodel'}) && ($dat->{'pcdriver'})) {
  4126.     $pcfilename = uc("$dat->{'pcmodel'}$dat->{'pcdriver'}");
  4127.     } else {
  4128.     my $driver = $dat->{'driver'};
  4129.     $driver =~ m!(^(.{1,8}))!;
  4130.     $pcfilename = uc($1);
  4131.     }
  4132.     $pcfilename = 'FOOMATIC' if !defined($pcfilename);
  4133.     my $model = $dat->{'model'};
  4134.     my $make = $dat->{'make'};
  4135.     my ($ieee1284,$pnpmake,$pnpmodel,$filename,$longname,
  4136.     $drivername,$nickname,$modelname) =
  4137.         getppdheaderdata($dat, $dat->{'driver'}, $dat->{'recdriver'});
  4138.     if ($ieee1284) {
  4139.     $ieee1284 = "*1284DeviceID: \"" . $ieee1284 . "\"";
  4140.     }
  4141.  
  4142.     # Add info about driver properties
  4143.     my $drvproperties = "";
  4144.     $drvproperties .= "*driverName $dat->{'driver'}/$dat->{'driver'}" .
  4145.     ($dat->{'shortdescription'} ? 
  4146.      " - $dat->{'shortdescription'}" : "") . 
  4147.      ": \"\"\n" if defined($dat->{'driver'});
  4148.     $drvproperties .= "*driverType $dat->{'type'}" .
  4149.     ($dat->{'type'} eq "G" ? "/GhostScript built-in" :
  4150.      ($dat->{'type'} eq "U" ? "/GhostScript Uniprint" :
  4151.       ($dat->{'type'} eq "F" ? "/Filter" :
  4152.        ($dat->{'type'} eq "C" ? "/CUPS Raster" :
  4153.         ($dat->{'type'} eq "V" ? "/OpenPrinting Vector" :
  4154.          ($dat->{'type'} eq "I" ? "/IJS" :
  4155.           ($dat->{'type'} eq "P" ? "/PostScript" : ""))))))) . 
  4156.           ": \"\"\n" if defined($dat->{'type'});
  4157.     $drvproperties .= "*driverUrl: \"$dat->{'url'}\"\n" if
  4158.     defined($dat->{'url'});
  4159.     if ((defined($dat->{'obsolete'})) &&
  4160.     ($dat->{'obsolete'} ne "0")) {
  4161.     $drvproperties .= "*driverObsolete: True\n";
  4162.     if ($dat->{'obsolete'} ne "1") {
  4163.         $drvproperties .= "*driverRecommendedReplacement: " .
  4164.         "\"$dat->{'obsolete'}\"\n";
  4165.     }
  4166.     } else {
  4167.     $drvproperties .= "*driverObsolete: False\n";
  4168.     }
  4169.     $drvproperties .= "*driverSupplier: \"$dat->{'supplier'}\"\n" if
  4170.     defined($dat->{'supplier'});
  4171.     $drvproperties .= "*driverManufacturerSupplied: " . 
  4172.     ($dat->{'manufacturersupplied'} eq "1" ? "True" : 
  4173.      ($dat->{make} =~ m,^($dat->{'manufacturersupplied'})$,i ? "True" :
  4174.       "False")) . "\n" if
  4175.     defined($dat->{'manufacturersupplied'});
  4176.     $drvproperties .= "*driverLicense: \"$dat->{'license'}\"\n" if
  4177.     defined($dat->{'license'});
  4178.     $drvproperties .= "*driverFreeSoftware: " . 
  4179.     ($dat->{'free'} ? "True" : "False") . "\n" if
  4180.     defined($dat->{'free'});
  4181.     if (defined($dat->{'supportcontacts'})) {
  4182.     foreach my $entry (@{$dat->{'supportcontacts'}}) {
  4183.         my $uclevel = uc(substr($entry->{'level'}, 0, 1)) .
  4184.         lc(substr($entry->{'level'}, 1));
  4185.         $drvproperties .= "*driverSupportContact${uclevel}: " .
  4186.         "\"$entry->{'url'} $entry->{'description'}\"\n";
  4187.     }
  4188.     }
  4189.     if (defined($dat->{'drvmaxresx'}) || defined($dat->{'drvmaxresy'})) {
  4190.     my ($maxresx, $maxresy);
  4191.     $maxresx = $dat->{'drvmaxresx'} if defined($dat->{'drvmaxresx'});
  4192.     $maxresy = $dat->{'drvmaxresy'} if defined($dat->{'drvmaxresy'});
  4193.     $maxresx = $maxresy if !$maxresx;
  4194.     $maxresy = $maxresx if !$maxresy;
  4195.     $drvproperties .= "*driverMaxResolution: " .
  4196.         "${maxresx} ${maxresy}\n";
  4197.     }
  4198.     $drvproperties .= "*driverColor: " . 
  4199.     ($dat->{'drvcolor'} ? "True" : "False") . "\n" if
  4200.     defined($dat->{'drvcolor'});
  4201.     $drvproperties .= "*driverTextSupport: $dat->{'text'}\n" if
  4202.     defined($dat->{'text'});
  4203.     $drvproperties .= "*driverLineartSupport: $dat->{'lineart'}\n" if
  4204.     defined($dat->{'lineart'});
  4205.     $drvproperties .= "*driverGraphicsSupport: $dat->{'graphics'}\n" if
  4206.     defined($dat->{'graphics'});
  4207.     $drvproperties .= "*driverPhotoSupport: $dat->{'photo'}\n" if
  4208.     defined($dat->{'photo'});
  4209.     $drvproperties .= "*driverSystemmLoad: $dat->{'load'}\n" if
  4210.     defined($dat->{'load'});
  4211.     $drvproperties .= "*driverRenderingSpeed: $dat->{'speed'}\n" if
  4212.     defined($dat->{'speed'});
  4213.     $drvproperties = "\n$drvproperties" if $drvproperties;
  4214.  
  4215.     # Do not use "," or "+" in the *ShortNickName to make the Windows
  4216.     # PostScript drivers happy
  4217.     my $shortnickname = "$make $model $drivername";
  4218.     if (length($shortnickname) > 31) {
  4219.     # ShortNickName too long? Shorten it.
  4220.     my %parts;
  4221.     $parts{'make'} = $make;
  4222.     $parts{'model'} = $model;
  4223.     $parts{'driver'} = $drivername;
  4224.     # Go through the three components, begin with model name, then
  4225.     # make and then driver
  4226.     for my $part (qw/model make driver/) {
  4227.         # Split the component into words, cutting always at the right edge
  4228.         # of the word. Cut also at a capital in the middle of the word
  4229.         # (ex: "S" in "PostScript").
  4230.         my @words = split(/(?<=[a-zA-Z])(?![a-zA-Z])|(?<=[a-z])(?=[A-Z])/,
  4231.                   $parts{$part});
  4232.         # Go through all words
  4233.         for (@words) {
  4234.         # Do not abbreviate words of less than 4 letters
  4235.         next if ($_ !~ /[a-zA-Z]{4,}$/);
  4236.         # How many letters did we chop off
  4237.         my $abbreviated = 0;
  4238.             while (1) {
  4239.             # Remove the last letter
  4240.             chop;
  4241.             $abbreviated ++;
  4242.             # Build the shortened component ...
  4243.             $parts{$part} = join('', @words);
  4244.             # ... and the ShortNickName
  4245.             $shortnickname =
  4246.             "$parts{'make'} $parts{'model'} $parts{'driver'}";
  4247.             # Stop if the ShostNickName has 30 characters or less
  4248.             # (we have still to add the abbreviation point), if there
  4249.             # is only one letter left, or if the manufacturer name
  4250.             # is reduced to three characters. Do not accept an
  4251.             # abbreviation of one character, as, taking the
  4252.             # abbreviation point into account, it does not save
  4253.             # a character.
  4254.             last if (((length($shortnickname) <= 30) &&
  4255.                   ($abbreviated != 1)) ||
  4256.                  ($_ !~ /[a-zA-Z]{2,}$/) ||
  4257.                  ((length($parts{'make'}) <= 3) &&
  4258.                   ($abbreviated != 1)));
  4259.         }
  4260.         #Abbreviation point
  4261.         if ($abbreviated) {
  4262.             $_ .= '.';
  4263.         }
  4264.         $parts{$part} = join('', @words);
  4265.         $shortnickname =
  4266.             "$parts{'make'} $parts{'model'} $parts{'driver'}";
  4267.         last if (length($shortnickname) <= 31);
  4268.         }
  4269.         last if (length($shortnickname) <= 31);
  4270.     }
  4271.     while ((length($shortnickname) > 31) &&
  4272.            (length($parts{'model'}) > 3)) {
  4273.         # ShortNickName too long? Remove last words from model name.
  4274.         $parts{'model'} =~
  4275.         s/(?<=[a-zA-Z0-9])[^a-zA-Z0-9]+[a-zA-Z0-9]*$//;
  4276.         $shortnickname =
  4277.         "$parts{'make'} $parts{'model'}, $parts{'driver'}";
  4278.     }
  4279.     if (length($shortnickname) > 31) {
  4280.         # If nothing else helps ...
  4281.         $shortnickname = substr($shortnickname, 0, 31);
  4282.     }
  4283.     }
  4284.  
  4285.     my $color;
  4286.     if ($dat->{'color'}) {
  4287.     $color = "*ColorDevice:    True\n*DefaultColorSpace: RGB";
  4288.     } else {
  4289.     $color = "*ColorDevice:    False\n*DefaultColorSpace: Gray";
  4290.     }
  4291.  
  4292.     # Clean up "<ppdentry>"s
  4293.     foreach my $type ('printerppdentry', 'driverppdentry', 'comboppdentry'){
  4294.     if (defined($dat->{$type})) {
  4295.         $dat->{$type} =~ s/^\s+//gm;
  4296.         $dat->{$type} =~ s/\s+$//gm;
  4297.         $dat->{$type} =~ s/^\n+//gs;
  4298.         $dat->{$type} =~ s/\n*$/\n/gs;
  4299.     } else {
  4300.         $dat->{$type} = '';
  4301.     }
  4302.     }
  4303.     my $extralines = $dat->{'printerppdentry'} .
  4304.                  $dat->{'driverppdentry'} .
  4305.              $dat->{'comboppdentry'};
  4306.  
  4307.     my $tmpl = get_tmpl();
  4308.     $tmpl =~ s!\@\@POSTPIPE\@\@!$postpipe!g;
  4309.     $tmpl =~ s!\@\@HEADCOMMENT\@\@!$headcomment!g;
  4310.     $tmpl =~ s!\@\@SAVETHISAS\@\@!$longname!g;
  4311.     $tmpl =~ s!\@\@PCFILENAME\@\@!$pcfilename!g;
  4312.     $tmpl =~ s!\@\@MANUFACTURER\@\@!$make!g;
  4313.     $tmpl =~ s!\@\@PNPMAKE\@\@!$pnpmake!g;
  4314.     $tmpl =~ s!\@\@PNPMODEL\@\@!$pnpmodel!g;
  4315.     $tmpl =~ s!\@\@MODEL\@\@!$modelname!g;
  4316.     $tmpl =~ s!\@\@NICKNAME\@\@!$nickname!g;
  4317.     $tmpl =~ s!\@\@SHORTNICKNAME\@\@!$shortnickname!g;
  4318.     $tmpl =~ s!\@\@COLOR\@\@!$color!g;
  4319.     $tmpl =~ s!\@\@IEEE1284\@\@!$ieee1284!g;
  4320.     $tmpl =~ s!\@\@DRIVERPROPERTIES\@\@!$drvproperties!g;
  4321.     $tmpl =~ s!\@\@OTHERSTUFF\@\@!$otherstuff!g;
  4322.     $tmpl =~ s!\@\@OPTIONS\@\@!$opts!g;
  4323.     $tmpl =~ s!\@\@EXTRALINES\@\@!$extralines!g;
  4324.     
  4325.     return ($tmpl);
  4326. }
  4327.  
  4328.  
  4329. # Utility function; returns content of a URL
  4330. sub getpage {
  4331.     my ($this, $url, $dontdie) = @_;
  4332.  
  4333.     my $failed = 0;
  4334.     my $page = undef;
  4335.     # Try it first to retrieve the page with the "wget" shell command
  4336.     if (-x $sysdeps->{'wget'}) {
  4337.     if (open PAGE, "$sysdeps->{'wget'} $url -O - 2>/dev/null |") {
  4338.         $page = join('', <PAGE>);
  4339.         close PAGE;
  4340.     } else {
  4341.         $failed = 1;
  4342.     }
  4343.     # Then try to retrieve the page with the "curl" shell command
  4344.     } elsif (-x $sysdeps->{'curl'}) {
  4345.     if (open PAGE, "$sysdeps->{'curl'} $url -o - 2>/dev/null |") {
  4346.         $page = join('', <PAGE>);
  4347.         close PAGE;
  4348.     } else {
  4349.         $failed = 1;
  4350.     }
  4351.     } else {
  4352.     warn("WARNING: No tool for downloading web content found, please install either\n\"wget\" or \"curl\"! The result you got may be incorrect!\n");
  4353.     }
  4354.  
  4355.     if ((!$page) || ($failed)) {
  4356.     if ($dontdie) {
  4357.         return undef;
  4358.     } else {
  4359.         die ("http error: " . $url . "\n");
  4360.     }
  4361.     }
  4362.  
  4363.     return $page;
  4364. }
  4365.  
  4366. # Determine the margins as needed by "*ImageableArea"
  4367. sub getmarginsformarginrecord {
  4368.     my ($margins, $width, $height, $pagesize) = @_;
  4369.     if (!defined($margins)) {
  4370.     # No margins defined? Return invalid margins
  4371.     return (undef, undef, undef, undef);
  4372.     }
  4373.     # Defaults
  4374.     my $unit = 'pt';
  4375.     my $absolute = 0;
  4376.     my ($left, $right, $top, $bottom) = (undef, undef, undef, undef);
  4377.     # Check the general margins and then the particular paper size
  4378.     for my $i ('_general', $pagesize) {
  4379.     # Skip a section if it is not defined
  4380.     next if (!defined($margins->{$i}));
  4381.     # Determine the factor to calculate the margin in points (pt)
  4382.     $unit = (defined($margins->{$i}{'unit'}) ?
  4383.          $margins->{$i}{'unit'} : $unit);
  4384.     my $unitfactor = 1.0; # Default unit is points
  4385.     if ($unit =~ /^p/i) {
  4386.         $unitfactor = 1.0;
  4387.     } elsif ($unit =~ /^in/i) {
  4388.         $unitfactor = 72.0;
  4389.     } elsif ($unit =~ /^cm$/i) {
  4390.         $unitfactor = 72.0/2.54;
  4391.     } elsif ($unit =~ /^mm$/i) {
  4392.         $unitfactor = 72.0/25.4;
  4393.     } elsif ($unit =~ /^dots(\d+)dpi$/i) {
  4394.         $unitfactor = 72.0/$1;
  4395.     }
  4396.     # Convert the values to points
  4397.     ($left, $right, $top, $bottom) =
  4398.         ((defined($margins->{$i}{'left'}) ?
  4399.           $margins->{$i}{'left'} * $unitfactor : $left),
  4400.          (defined($margins->{$i}{'right'}) ?
  4401.           $margins->{$i}{'right'} * $unitfactor : $right),
  4402.          (defined($margins->{$i}{'top'}) ?
  4403.           $margins->{$i}{'top'} * $unitfactor : $top),
  4404.          (defined($margins->{$i}{'bottom'}) ?
  4405.           $margins->{$i}{'bottom'} * $unitfactor : $bottom));
  4406.     # Determine the absolute values
  4407.     $absolute = (defined($margins->{$i}{'absolute'}) ?
  4408.              $margins->{$i}{'absolute'} : $absolute);
  4409.     if (!$absolute){
  4410.         if (defined($margins->{$i}{'right'})) {
  4411.         $right = $width - $right;
  4412.         }
  4413.         if (defined($margins->{$i}{'top'})) {
  4414.         $top = $height - $top;
  4415.         }
  4416.     }
  4417.     }
  4418.     $left = sprintf("%.2f", $left) if $left =~ /\./;
  4419.     $right = sprintf("%.2f", $right) if $right =~ /\./;
  4420.     $top = sprintf("%.2f", $top) if $top =~ /\./;
  4421.     $bottom = sprintf("%.2f", $bottom) if $bottom =~ /\./;
  4422.     return ($left, $right, $top, $bottom);
  4423. }
  4424.  
  4425. sub getmargins {
  4426.     my ($dat, $width, $height, $pagesize) = @_;
  4427.     # Determine the unprintable margins
  4428.     my ($left, $right, $top, $bottom) = (undef, undef, undef, undef);
  4429.     # Margins from printer database entry
  4430.     my ($pleft, $pright, $ptop, $pbottom) =
  4431.     getmarginsformarginrecord($dat->{'printermargins'}, 
  4432.                   $width, $height, $pagesize);
  4433.     # Margins from driver database entry
  4434.     my ($dleft, $dright, $dtop, $dbottom) =
  4435.     getmarginsformarginrecord($dat->{'drivermargins'}, 
  4436.                   $width, $height, $pagesize);
  4437.     # Margins from printer/driver combo
  4438.     my ($cleft, $cright, $ctop, $cbottom) =
  4439.     getmarginsformarginrecord($dat->{'combomargins'}, 
  4440.                   $width, $height, $pagesize);
  4441.     # Left margin
  4442.     if (defined($pleft)) {$left = $pleft};
  4443.     if (defined($dleft) &&
  4444.     (!defined($left) || ($dleft > $left))) {$left = $dleft};
  4445.     if (defined($cleft) &&
  4446.     (!defined($left) || ($cleft > $left))) {$left = $cleft};
  4447.     # Right margin
  4448.     if (defined($pright)) {$right = $pright};
  4449.     if (defined($dright) &&
  4450.     (!defined($right) || ($dright < $right))) {$right = $dright};
  4451.     if (defined($cright) &&
  4452.     (!defined($right) || ($cright < $right))) {$right = $cright};
  4453.     # Top margin
  4454.     if (defined($ptop)) {$top = $ptop};
  4455.     if (defined($dtop) &&
  4456.     (!defined($top) || ($dtop < $top))) {$top = $dtop};
  4457.     if (defined($ctop) &&
  4458.     (!defined($top) || ($ctop < $top))) {$top = $ctop};
  4459.     # Bottom margin
  4460.     if (defined($pbottom)) {$bottom = $pbottom};
  4461.     if (defined($dbottom) &&
  4462.     (!defined($bottom) || ($dbottom > $bottom))) {$bottom = $dbottom};
  4463.     if (defined($cbottom) &&
  4464.     (!defined($bottom) || ($dbottom > $bottom))) {$bottom = $cbottom};
  4465.     # Safe margins when margin info is missing
  4466.     my $tborder = 36;
  4467.     my $bborder = 36;
  4468.     my $lborder = 18;
  4469.     my $rborder = 18;
  4470.     $left = $lborder if !defined($left);
  4471.     $right = $width - $rborder if !defined($right);
  4472.     $top = $height - $tborder if !defined($top);
  4473.     $bottom = $bborder if !defined($bottom);
  4474.     # If we entered with $width == 0 and $height == 0, we mean
  4475.     # relative margins, so correct the signs
  4476.     if ($width == 0) {$right = -$right};
  4477.     if ($height == 0) {$top = -$top};
  4478.     # Clean up output
  4479.     $left =~ s/^\s*-0\s*$/0/;
  4480.     $right =~ s/^\s*-0\s*$/0/;
  4481.     $top =~ s/^\s*-0\s*$/0/;
  4482.     $bottom =~ s/^\s*-0\s*$/0/;
  4483.     # Return the results
  4484.     return ($left, $right, $top, $bottom);
  4485. }
  4486.  
  4487. # Generate a translation/longname from a shortname
  4488. sub longname {
  4489.     my $shortname = $_[0];
  4490.     # A space before every upper-case letter in the middle preceeded by
  4491.     # a lower-case one
  4492.     $shortname =~ s/([a-z])([A-Z])/$1 $2/g;
  4493.     # If there are three or more upper-case letters, assume the last as
  4494.     # the beginning of the next word, the others as an abbreviation
  4495.     $shortname =~ s/([A-Z][A-Z]+)([A-Z][a-z])/$1 $2/g;
  4496.     return $shortname;
  4497. }
  4498.  
  4499. # Prepare strings for being part of an HTML document by, converting
  4500. # "<" to "<", ">" to ">", "&" to "&", "\"" to """,
  4501. # and "'" to  "'"
  4502. sub htmlify {
  4503.     my $str = $_[0];
  4504.     $str =~ s!&!&!g;
  4505.     $str =~ s/\</\</g;
  4506.     $str =~ s/\>/\>/g;
  4507.     $str =~ s/\"/\"/g;
  4508.     $str =~ s/\'/\'/g;
  4509.     return $str;
  4510. }
  4511.  
  4512. # This splits RIP directives (PostScript comments which are
  4513. # foomatic-rip uses to build the RIP command line) into multiple lines
  4514. # of a fixed length, to avoid lines longer than 255 characters. The
  4515. # PPD specification does not allow such long lines.
  4516. sub ripdirective {
  4517.     my ($header, $content) = ($_[0], htmlify($_[1]));
  4518.     # If possible, make lines of this length
  4519.     my $maxlength = 72;
  4520.     # Header of continuation line
  4521.     my $continueheader = "";
  4522.     # Two subsequent ampersands are not possible in an htmlified string,
  4523.     # so we can use them at the line end to mark that the current line
  4524.     # continues on the next line. A newline without this is also a newline
  4525.     # in the decoded string
  4526.     my $continuelineend = "&&";
  4527.     # output string
  4528.     my $out;
  4529.     # The colon and the quote after the header must be on the line with
  4530.     # the header
  4531.     $header .= ": \"";
  4532.     # How much of the current line is left?
  4533.     my $freelength = $maxlength - length($header) -
  4534.     length($continuelineend);
  4535.     # Add the header
  4536.     if ($freelength < 0) {
  4537.     # header longer than $maxlength, don't break it
  4538.     $out = "$header$continuelineend\n$continueheader";
  4539.     $freelength = $maxlength - length($continueheader) -
  4540.         length($continuelineend);
  4541.     } else {
  4542.     $out = "$header";
  4543.     }
  4544.     $content .= "\"";
  4545.     # Go through every line of the $content
  4546.     for my $l (split ("\n", $content)) {
  4547.     while ($l) {
  4548.         # Take off $maxlength portions until the string is used up
  4549.         if (length($l) < $freelength) {
  4550.         $freelength = length($l);
  4551.         }
  4552.         my $line = substr($l, 0, $freelength, "");
  4553.         # Add the portion 
  4554.         $out .= $line;
  4555.         # Finish the line
  4556.         $freelength = $maxlength - length($continueheader) -
  4557.         length($continuelineend);
  4558.         if ($l) {
  4559.         # Line continues in next line
  4560.         $out .= "$continuelineend\n$continueheader";
  4561.         } else {
  4562.         # line ends
  4563.         $out .= "\n";
  4564.         last;
  4565.         }
  4566.     }
  4567.     }
  4568.     # Remove trailing newline
  4569.     $out = substr($out, 0, -1);
  4570.     return $out;
  4571. }
  4572.  
  4573.  
  4574. # PPD boilerplate template
  4575.  
  4576. sub get_tmpl_paperdimension {
  4577.     return <<ENDPDTEMPL;
  4578. *% Generic PaperDimension; evidently there was no normal PageSize argument
  4579.  
  4580. *DefaultPaperDimension: Letter
  4581. *PaperDimension Letter:    "612 792"
  4582. *PaperDimension Legal:    "612 1008"
  4583. *PaperDimension A4:    "595 842"
  4584. ENDPDTEMPL
  4585. }
  4586.  
  4587. sub get_tmpl {
  4588.     return <<ENDTMPL;
  4589. *PPD-Adobe: "4.3"
  4590. \@\@POSTPIPE\@\@*%
  4591. \@\@HEADCOMMENT\@\@
  4592. *%
  4593. *% You may save this file as '\@\@SAVETHISAS\@\@'
  4594. *%
  4595. *%
  4596. *FormatVersion:    "4.3"
  4597. *FileVersion:    "1.1"
  4598. *LanguageVersion: English 
  4599. *LanguageEncoding: ISOLatin1
  4600. *PCFileName:    "\@\@PCFILENAME\@\@.PPD"
  4601. *Manufacturer:    "\@\@MANUFACTURER\@\@"
  4602. *Product:    "(\@\@PNPMODEL\@\@)"
  4603. *cupsVersion:    1.0
  4604. *cupsManualCopies: True
  4605. *cupsModelNumber:  2
  4606. *cupsFilter:    "application/vnd.cups-postscript 100 foomatic-rip"
  4607. *cupsFilter:    "application/vnd.cups-pdf 0 foomatic-rip"
  4608. *%pprRIP:        foomatic-rip other
  4609. *ModelName:     "\@\@MODEL\@\@"
  4610. *ShortNickName: "\@\@SHORTNICKNAME\@\@"
  4611. *NickName:      "\@\@NICKNAME\@\@"
  4612. *PSVersion:    "(3010.000) 550"
  4613. *PSVersion:    "(3010.000) 651"
  4614. *PSVersion:    "(3010.000) 652"
  4615. *PSVersion:    "(3010.000) 653"
  4616. *PSVersion:    "(3010.000) 704"
  4617. *PSVersion:    "(3010.000) 705"
  4618. *PSVersion:    "(3010.000) 800"
  4619. *LanguageLevel:    "3"
  4620. \@\@COLOR\@\@
  4621. *FileSystem:    False
  4622. *Throughput:    "1"
  4623. *LandscapeOrientation: Plus90
  4624. *TTRasterizer:    Type42
  4625. \@\@IEEE1284\@\@
  4626. \@\@DRIVERPROPERTIES\@\@
  4627. \@\@EXTRALINES\@\@
  4628. \@\@OTHERSTUFF\@\@
  4629.  
  4630. \@\@OPTIONS\@\@
  4631.  
  4632. *% Generic boilerplate PPD stuff as standard PostScript fonts and so on
  4633.  
  4634. *DefaultFont: Courier
  4635. *Font AvantGarde-Book: Standard "(001.006S)" Standard ROM
  4636. *Font AvantGarde-BookOblique: Standard "(001.006S)" Standard ROM
  4637. *Font AvantGarde-Demi: Standard "(001.007S)" Standard ROM
  4638. *Font AvantGarde-DemiOblique: Standard "(001.007S)" Standard ROM
  4639. *Font Bookman-Demi: Standard "(001.004S)" Standard ROM
  4640. *Font Bookman-DemiItalic: Standard "(001.004S)" Standard ROM
  4641. *Font Bookman-Light: Standard "(001.004S)" Standard ROM
  4642. *Font Bookman-LightItalic: Standard "(001.004S)" Standard ROM
  4643. *Font Courier: Standard "(002.004S)" Standard ROM
  4644. *Font Courier-Bold: Standard "(002.004S)" Standard ROM
  4645. *Font Courier-BoldOblique: Standard "(002.004S)" Standard ROM
  4646. *Font Courier-Oblique: Standard "(002.004S)" Standard ROM
  4647. *Font Helvetica: Standard "(001.006S)" Standard ROM
  4648. *Font Helvetica-Bold: Standard "(001.007S)" Standard ROM
  4649. *Font Helvetica-BoldOblique: Standard "(001.007S)" Standard ROM
  4650. *Font Helvetica-Narrow: Standard "(001.006S)" Standard ROM
  4651. *Font Helvetica-Narrow-Bold: Standard "(001.007S)" Standard ROM
  4652. *Font Helvetica-Narrow-BoldOblique: Standard "(001.007S)" Standard ROM
  4653. *Font Helvetica-Narrow-Oblique: Standard "(001.006S)" Standard ROM
  4654. *Font Helvetica-Oblique: Standard "(001.006S)" Standard ROM
  4655. *Font NewCenturySchlbk-Bold: Standard "(001.009S)" Standard ROM
  4656. *Font NewCenturySchlbk-BoldItalic: Standard "(001.007S)" Standard ROM
  4657. *Font NewCenturySchlbk-Italic: Standard "(001.006S)" Standard ROM
  4658. *Font NewCenturySchlbk-Roman: Standard "(001.007S)" Standard ROM
  4659. *Font Palatino-Bold: Standard "(001.005S)" Standard ROM
  4660. *Font Palatino-BoldItalic: Standard "(001.005S)" Standard ROM
  4661. *Font Palatino-Italic: Standard "(001.005S)" Standard ROM
  4662. *Font Palatino-Roman: Standard "(001.005S)" Standard ROM
  4663. *Font Symbol: Special "(001.007S)" Special ROM
  4664. *Font Times-Bold: Standard "(001.007S)" Standard ROM
  4665. *Font Times-BoldItalic: Standard "(001.009S)" Standard ROM
  4666. *Font Times-Italic: Standard "(001.007S)" Standard ROM
  4667. *Font Times-Roman: Standard "(001.007S)" Standard ROM
  4668. *Font ZapfChancery-MediumItalic: Standard "(001.007S)" Standard ROM
  4669. *Font ZapfDingbats: Special "(001.004S)" Standard ROM
  4670.  
  4671. ENDTMPL
  4672. }
  4673.  
  4674. # Determine the paper width and height in points from a given paper size
  4675. # name. Used for the "PaperDimension" and "ImageableArea" entries in PPD
  4676. # files.
  4677. #
  4678. # The paper sizes in the list are all sizes known to GhostScript, all
  4679. # of Gutenprint, all sizes of HPIJS, and some others found in the data
  4680. # of printer drivers.
  4681.  
  4682. sub getpapersize {
  4683.     my $papersize = lc(join('', @_));
  4684.  
  4685.     my @sizetable = (
  4686.     ['germanlegalfanfold', '612 936'],
  4687.     ['halfletter',         '396 612'],
  4688.     ['letterwide',         '647 957'],
  4689.     ['lettersmall',        '612 792'],
  4690.     ['letter',             '612 792'],
  4691.     ['legal',              '612 1008'],
  4692.     ['postcard',           '283 416'],
  4693.     ['tabloid',            '792 1224'],
  4694.     ['ledger',             '1224 792'],
  4695.     ['tabloidextra',       '864 1296'],
  4696.     ['statement',          '396 612'],
  4697.     ['manual',             '396 612'],
  4698.     ['executive',          '522 756'],
  4699.     ['folio',              '612 936'],
  4700.     ['archa',              '648 864'],
  4701.     ['archb',              '864 1296'],
  4702.     ['archc',              '1296 1728'],
  4703.     ['archd',              '1728 2592'],
  4704.     ['arche',              '2592 3456'],
  4705.     ['usaarch',            '648 864'],
  4706.     ['usbarch',            '864 1296'],
  4707.     ['uscarch',            '1296 1728'],
  4708.     ['usdarch',            '1728 2592'],
  4709.     ['usearch',            '2592 3456'],
  4710.     ['a2.*invit.*',        '315 414'],
  4711.     ['b6-c4',              '354 918'],
  4712.     ['c7-6',               '229 459'],
  4713.     ['supera3-b',          '932 1369'],
  4714.     ['a3wide',             '936 1368'],
  4715.     ['a4wide',             '633 1008'],
  4716.     ['a4small',            '595 842'],
  4717.     ['sra4',               '637 907'],
  4718.     ['sra3',               '907 1275'],
  4719.     ['sra2',               '1275 1814'],
  4720.     ['sra1',               '1814 2551'],
  4721.     ['sra0',               '2551 3628'],
  4722.     ['ra4',                '609 864'],
  4723.     ['ra3',                '864 1218'],
  4724.     ['ra2',                '1218 1729'],
  4725.     ['ra1',                '1729 2437'],
  4726.     ['ra0',                '2437 3458'],
  4727.     ['a10',                '74 105'],
  4728.     ['a9',                 '105 148'],
  4729.     ['a8',                 '148 210'],
  4730.     ['a7',                 '210 297'],
  4731.     ['a6',                 '297 420'],
  4732.     ['a5',                 '420 595'],
  4733.     ['a4',                 '595 842'],
  4734.     ['a3',                 '842 1191'],
  4735.     ['a2',                 '1191 1684'],
  4736.     ['a1',                 '1684 2384'],
  4737.     ['a0',                 '2384 3370'],
  4738.     ['2a',                 '3370 4768'],
  4739.     ['4a',                 '4768 6749'],
  4740.     ['c10',                '79 113'],
  4741.     ['c9',                 '113 161'],
  4742.     ['c8',                 '161 229'],
  4743.     ['c7',                 '229 323'],
  4744.     ['c6',                 '323 459'],
  4745.     ['c5',                 '459 649'],
  4746.     ['c4',                 '649 918'],
  4747.     ['c3',                 '918 1298'],
  4748.     ['c2',                 '1298 1836'],
  4749.     ['c1',                 '1836 2599'],
  4750.     ['c0',                 '2599 3676'],
  4751.     ['b10.*jis',           '90 127'],
  4752.     ['b9.*jis',            '127 180'],
  4753.     ['b8.*jis',            '180 257'],
  4754.     ['b7.*jis',            '257 362'],
  4755.     ['b6.*jis',            '362 518'],
  4756.     ['b5.*jis',            '518 727'],
  4757.     ['b4.*jis',            '727 1029'],
  4758.     ['b3.*jis',            '1029 1459'],
  4759.     ['b2.*jis',            '1459 2063'],
  4760.     ['b1.*jis',            '2063 2919'],
  4761.     ['b0.*jis',            '2919 4127'],
  4762.     ['jis.*b10',           '90 127'],
  4763.     ['jis.*b9',            '127 180'],
  4764.     ['jis.*b8',            '180 257'],
  4765.     ['jis.*b7',            '257 362'],
  4766.     ['jis.*b6',            '362 518'],
  4767.     ['jis.*b5',            '518 727'],
  4768.     ['jis.*b4',            '727 1029'],
  4769.     ['jis.*b3',            '1029 1459'],
  4770.     ['jis.*b2',            '1459 2063'],
  4771.     ['jis.*b1',            '2063 2919'],
  4772.     ['jis.*b0',            '2919 4127'],
  4773.     ['b10.*iso',           '87 124'],
  4774.     ['b9.*iso',            '124 175'],
  4775.     ['b8.*iso',            '175 249'],
  4776.     ['b7.*iso',            '249 354'],
  4777.     ['b6.*iso',            '354 498'],
  4778.     ['b5.*iso',            '498 708'],
  4779.     ['b4.*iso',            '708 1000'],
  4780.     ['b3.*iso',            '1000 1417'],
  4781.     ['b2.*iso',            '1417 2004'],
  4782.     ['b1.*iso',            '2004 2834'],
  4783.     ['b0.*iso',            '2834 4008'],
  4784.     ['2b.*iso',            '4008 5669'],
  4785.     ['4b.*iso',            '5669 8016'],
  4786.     ['iso.*b10',           '87 124'],
  4787.     ['iso.*b9',            '124 175'],
  4788.     ['iso.*b8',            '175 249'],
  4789.     ['iso.*b7',            '249 354'],
  4790.     ['iso.*b6',            '354 498'],
  4791.     ['iso.*b5',            '498 708'],
  4792.     ['iso.*b4',            '708 1000'],
  4793.     ['iso.*b3',            '1000 1417'],
  4794.     ['iso.*b2',            '1417 2004'],
  4795.     ['iso.*b1',            '2004 2834'],
  4796.     ['iso.*b0',            '2834 4008'],
  4797.     ['iso.*2b',            '4008 5669'],
  4798.     ['iso.*4b',            '5669 8016'],
  4799.     ['b10envelope',        '87 124'],
  4800.     ['b9envelope',         '124 175'],
  4801.     ['b8envelope',         '175 249'],
  4802.     ['b7envelope',         '249 354'],
  4803.     ['b6envelope',         '354 498'],
  4804.     ['b5envelope',         '498 708'],
  4805.     ['b4envelope',         '708 1000'],
  4806.     ['b3envelope',         '1000 1417'],
  4807.     ['b2envelope',         '1417 2004'],
  4808.     ['b1envelope',         '2004 2834'],
  4809.     ['b0envelope',         '2834 4008'],
  4810.     ['b10',                '87 124'],
  4811.     ['b9',                 '124 175'],
  4812.     ['b8',                 '175 249'],
  4813.     ['b7',                 '249 354'],
  4814.     ['b6',                 '354 498'],
  4815.     ['b5',                 '498 708'],
  4816.     ['b4',                 '708 1000'],
  4817.     ['b3',                 '1000 1417'],
  4818.     ['b2',                 '1417 2004'],
  4819.     ['b1',                 '2004 2834'],
  4820.     ['b0',                 '2834 4008'],
  4821.     ['monarch',            '279 540'],
  4822.     ['dl',                 '311 623'],
  4823.     ['com10',              '297 684'],
  4824.     ['com.*10',            '297 684'],
  4825.     ['env10',              '297 684'],
  4826.     ['env.*10',            '297 684'],
  4827.     ['hagaki',             '283 420'],
  4828.     ['oufuku',             '420 567'],
  4829.     ['kaku',               '680 941'],
  4830.     ['long.*3',            '340 666'],
  4831.     ['long.*4',            '255 581'],
  4832.     ['foolscap',           '576 936'],
  4833.     ['flsa',               '612 936'],
  4834.     ['flse',               '648 936'],
  4835.     ['photo100x150',       '283 425'],
  4836.     ['photo200x300',       '567 850'],
  4837.     ['photofullbleed',     '298 440'],
  4838.     ['photo4x6',           '288 432'],
  4839.     ['photo',              '288 432'],
  4840.     ['wide',               '977 792'],
  4841.     ['card148',            '419 297'],
  4842.     ['envelope132x220',    '374 623'],
  4843.     ['envelope61/2',       '468 260'],
  4844.     ['supera',             '644 1008'],
  4845.     ['superb',             '936 1368'],
  4846.     ['fanfold5',           '612 792'],
  4847.     ['fanfold4',           '612 864'],
  4848.     ['fanfold3',           '684 792'],
  4849.     ['fanfold2',           '864 612'],
  4850.     ['fanfold1',           '1044 792'],
  4851.     ['fanfold',            '1071 792'],
  4852.     ['panoramic',          '595 1683'],
  4853.     ['plotter.*size.*a',   '612 792'],
  4854.     ['plotter.*size.*b',   '792 1124'],
  4855.     ['plotter.*size.*c',   '1124 1584'],
  4856.     ['plotter.*size.*d',   '1584 2448'],
  4857.     ['plotter.*size.*e',   '2448 3168'],
  4858.     ['plotter.*size.*f',   '3168 4896'],
  4859.     ['archlarge',          '162 540'],
  4860.     ['standardaddr',       '81 252'],
  4861.     ['largeaddr',          '101 252'],
  4862.     ['suspensionfile',     '36 144'],
  4863.     ['videospine',         '54 423'],
  4864.     ['badge',              '153 288'],
  4865.     ['archsmall',          '101 540'],
  4866.     ['videotop',           '130 223'],
  4867.     ['diskette',           '153 198'],
  4868.     ['76\.2mmroll',        '216 0'],
  4869.     ['69\.5mmroll',        '197 0'],
  4870.     ['roll',               '612 0'],
  4871.     ['custom',             '0 0']
  4872.     );
  4873.  
  4874.     # Remove prefixes which sometimes could appear
  4875.     $papersize =~ s/form_//;
  4876.  
  4877.     # Check whether the paper size name is in the list above
  4878.     for my $item (@sizetable) {
  4879.     if ($papersize =~ /@{$item}[0]/) {
  4880.         return @{$item}[1];
  4881.     }
  4882.     }
  4883.  
  4884.     # Check if we have a "<Width>x<Height>" format, assume the numbers are
  4885.     # given in inches
  4886.     if ($papersize =~ /(\d+)x(\d+)/) {
  4887.     my $w = $1 * 72;
  4888.     my $h = $2 * 72;
  4889.     return sprintf("%d %d", $w, $h);
  4890.     }
  4891.  
  4892.     # Check if we have a "w<Width>h<Height>" format, assume the numbers are
  4893.     # given in points
  4894.     if ($papersize =~ /w(\d+)h(\d+)/) {
  4895.     return "$1 $2";
  4896.     }
  4897.  
  4898.     # Check if we have a "w<Width>" format, assume roll paper with the given
  4899.     # width in points
  4900.     if ($papersize =~ /w(\d+)/) {
  4901.     return "$1 0";
  4902.     }
  4903.  
  4904.     # This paper size is absolutely unknown, issue a warning
  4905.     warn "WARNING: Unknown paper size: $papersize!";
  4906.     return "0 0";
  4907. }
  4908.  
  4909. # Get documentation for the printer/driver pair to print out. For
  4910. # "Execution Details" section of driver web pages on OpenPrinting
  4911.  
  4912. sub getexecdocs {
  4913.  
  4914.     my ($this) = $_[0];
  4915.  
  4916.     my $dat = $this->{'dat'};
  4917.  
  4918.     my @docs;
  4919.     
  4920.     # Construct the proper command line.
  4921.     my $commandline = htmlify($dat->{'cmd'});
  4922.  
  4923.     if ($commandline eq "") {return ();}
  4924.  
  4925.     my @letters = qw/A B C D E F G H I J K L M Z/;
  4926.     
  4927.     for my $spot (@letters) {
  4928.     
  4929.     if($commandline =~ m!\%$spot!) {
  4930.  
  4931.         my $arg;
  4932.       argument:
  4933.         for $arg (@{$dat->{'args'}}) {
  4934. #        for $arg (sort { $a->{'order'} <=> $b->{'order'} } 
  4935. #              @{$dat->{'args'}}) {
  4936.         
  4937.         # Only do arguments that go in this spot
  4938.         next argument if ($arg->{'spot'} ne $spot);
  4939.         # PJL arguments are not inserted at a spot in the command
  4940.         # line
  4941.         next argument if ($arg->{'style'} eq 'J');
  4942.         # Composite options are not interesting here
  4943.         next argument if ($arg->{'style'} eq 'X');
  4944.         
  4945.         my $name = htmlify($arg->{'name'});
  4946.         my $varname = htmlify($arg->{'varname'});
  4947.         my $cmd = htmlify($arg->{'proto'});
  4948.         my $comment = htmlify($arg->{'comment'});
  4949.         my $placeholder = "</TT><I><$name></I><TT>";
  4950.         my $default = htmlify($arg->{'default'});
  4951.         my $type = $arg->{'type'};
  4952.         my $cmdvar = "";
  4953.         my $gsarg1 = "";
  4954.         my $gsarg2 = "";
  4955.         if ($arg->{'style'} eq 'G') {
  4956.             $gsarg1 = ' -c "';
  4957.             $gsarg2 = '"';
  4958.             $cmd =~ s/\"/\\\"/g;
  4959.         }
  4960.         #my $leftbr = ($arg->{'required'} ? "" : "[");
  4961.         #my $rightbr = ($arg->{'required'} ? "" : "]");
  4962.         my $leftbr = "";
  4963.         my $rightbr = "";
  4964.     
  4965.         if ($type eq 'bool') {
  4966.             $cmdvar = "$leftbr$gsarg1$cmd$gsarg2$rightbr";
  4967.         } elsif ($type eq 'int' or $type eq 'float') {
  4968.             $cmdvar = sprintf("$leftbr$gsarg1$cmd$gsarg2$rightbr",$placeholder);
  4969.         } elsif ($type eq 'enum') {
  4970.             my $val;
  4971.             if ($val=valbyname($arg,$default)) {
  4972.             $cmdvar = sprintf("$leftbr$gsarg1$cmd$gsarg2$rightbr",
  4973.                       $placeholder);
  4974.             }
  4975.         }
  4976.         
  4977.         # Insert the processed argument in the commandline
  4978.         # just before every occurance of the spot marker.
  4979.         $cmdvar =~ s!^\[\ !\ \[!;
  4980.         $commandline =~ s!\%$spot!$cmdvar\%$spot!g;
  4981.         }
  4982.         
  4983.         # Remove the letter markers from the commandline
  4984.         $commandline =~ s!\%$spot!!g;
  4985.         
  4986.     }
  4987.     
  4988.     }
  4989.  
  4990.     $dat->{'excommandline'} = $commandline;
  4991.  
  4992.     push(@docs, "<B>Command Line</B><P>");
  4993.     push(@docs, "<BLOCKQUOTE><TT>$commandline</TT></BLOCKQUOTE><P>");
  4994.  
  4995.     my ($arg, @doctmp);
  4996.     my @pjlcommands = ();
  4997.   argt:
  4998.     for $arg (@{$dat->{'args'}}) {
  4999. #    for $arg (sort { $a->{'order'} <=> $b->{'order'} } 
  5000. #          @{$dat->{'args'}}) {
  5001.  
  5002.     # Composite options are not interesting here
  5003.     next argt if ($arg->{'style'} eq 'X');
  5004.  
  5005.     # Make sure that the longname/translation exists
  5006.     if (!$arg->{'comment'}) {
  5007.         $arg->{'comment'} = longname($arg->{'name'});
  5008.     }
  5009.  
  5010.     my $name = htmlify($arg->{'name'});
  5011.     my $cmd = htmlify($arg->{'proto'});
  5012.     my $comment = htmlify($arg->{'comment'});
  5013.     my $placeholder = "</TT><I><$name></I><TT>";
  5014.     if ($arg->{'style'} eq 'J') {
  5015.         $cmd = "\@PJL $cmd";
  5016.         my $sprintfcmd = $cmd;
  5017.         $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  5018.         push (@pjlcommands, sprintf($sprintfcmd, $placeholder));
  5019.     }
  5020.  
  5021.     my $default = htmlify($arg->{'default'});
  5022.     my $type = $arg->{'type'};
  5023.     
  5024.     my $required = ($arg->{'required'} ? " required" : "n optional");
  5025.     my $pjl = ($arg->{'style'} eq 'J' ? "PJL " : "");
  5026.  
  5027.     if ($type eq 'bool') {
  5028.         my $name_false = htmlify($arg->{'name_false'});
  5029.         push(@doctmp,
  5030.          "<DL><DT><I>$name</I></DT>",
  5031.          "<DD>A$required boolean ${pjl}argument meaning $name if present or $name_false if not.<BR>",
  5032.          "$comment<BR>",
  5033.          "Prototype: <TT>$cmd</TT><BR>",
  5034.          "Default: ", $default ? "True" : "False",
  5035.          "</DD></DL><P>"
  5036.          );
  5037.  
  5038.     } elsif ($type eq 'int' or $type eq 'float') {
  5039.         my $max = (defined($arg->{'max'}) ? $arg->{'max'} : "none");
  5040.         my $min = (defined($arg->{'min'}) ? $arg->{'min'} : "none");
  5041.         my $sprintfcmd = $cmd;
  5042.         $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  5043.         push(@doctmp,
  5044.          "<DL><DT><I>$name</I></DT>",
  5045.          "<DD>A$required $type ${pjl}argument.<BR>",
  5046.          "$comment<BR>",
  5047.          "Prototype: <TT>", sprintf($sprintfcmd, $placeholder),
  5048.          "</TT><BR>",
  5049.          "Default: <TT>$default</TT><BR>",
  5050.          "Range: <TT>$min <= $placeholder <= $max</TT>",
  5051.          "</DD></DL><P>"
  5052.          );
  5053.  
  5054.     } elsif ($type eq 'enum') {
  5055.         my ($val, $defstr);
  5056.         my (@choicelist) = ();
  5057.  
  5058.         for $val (@{$arg->{'vals'}}) {
  5059.  
  5060.         # Make sure that the longname/translation exists
  5061.         if (!$val->{'comment'}) {
  5062.             $val->{'comment'} = longname($val->{'value'});
  5063.         }
  5064.  
  5065.         my ($value, $comment, $driverval) = 
  5066.             (htmlify($val->{'value'}),
  5067.              htmlify($val->{'comment'}),
  5068.              htmlify($val->{'driverval'}));
  5069.  
  5070.         if (defined($driverval)) {
  5071.             if ($driverval eq "") {
  5072.             push(@choicelist,
  5073.                  "<LI>$value: $comment (<TT>$placeholder</TT> is left blank)</LI>");
  5074.             } else {
  5075.             my $widthheight = "";
  5076.             if (($name eq "PageSize") && ($value eq "Custom")) {
  5077.                 my $width = "</TT><I><Width></I><TT>";
  5078.                 my $height = "</TT><I><Height></I><TT>";
  5079.                 $driverval =~ s/\%0/$width/ or
  5080.                             $driverval =~ s/(\W)0(\W)/$1$width$2/ or
  5081.                             $driverval =~ s/^0(\W)/$width$1/m or
  5082.                             $driverval =~ s/(\W)0$/$1$width/m or
  5083.                             $driverval =~ s/^0$/$width/m;
  5084.                             $driverval =~ s/\%1/$height/ or
  5085.                             $driverval =~ s/(\W)0(\W)/$1$height$2/ or
  5086.                             $driverval =~ s/^0(\W)/$height$1/m or
  5087.                             $driverval =~ s/(\W)0$/$1$height/m or
  5088.                             $driverval =~ s/^0$/$height/m;
  5089.                 $widthheight = ", <I><Width></I> and <I><Height></I> are the page dimensions in points, 1/72 inches";
  5090.             }
  5091.             push(@choicelist,
  5092.                  "<LI>$value: $comment (<TT>$placeholder</TT> is '<TT>$driverval</TT>'$widthheight)</LI>");
  5093.             }
  5094.         } else {
  5095.             push(@choicelist,
  5096.              "<LI>$value: $comment (<TT>$placeholder</TT> is '<TT>$value</TT>')</LI>");
  5097.         }
  5098.         }
  5099.  
  5100.         my $sprintfcmd = $cmd;
  5101.         $sprintfcmd =~ s/\%(?!s)/\%\%/g;
  5102.         push(@doctmp,
  5103.          "<DL><DT><I>$name</I></DT>",
  5104.          "<DD>A$required enumerated choice ${pjl}argument.<BR>",
  5105.          "$comment<BR>",
  5106.          "Prototype: <TT>", sprintf($sprintfcmd, $placeholder),
  5107.          "</TT><BR>",
  5108.          "Default: $default",
  5109.          "<UL>", 
  5110.          join("", @choicelist), 
  5111.          "</UL></DD></DL><P>"
  5112.          );
  5113.  
  5114.     }
  5115.     }
  5116.  
  5117.     # Instructions for PJL commands
  5118.     if (($#pjlcommands > -1) && (defined($dat->{'pjl'}))) {
  5119.     #if (($#pjlcommands > -1)) {
  5120.     my @pjltmp;
  5121.     push(@pjltmp,
  5122.          "PJL arguments are not put into the command line, they must be put into a PJL header which is prepended to the actual job data which is generated by the command line shown above and sent to the printer. After the job data one can reset the printer via PJL. So a complete job looks as follows:<BLOCKQUOTE>",
  5123.          "<I><ESC></I>",
  5124.          # The "JOB" PJL command is not supported by all printers
  5125.          "<TT>%-12345X\@PJL</TT><BR>");
  5126.          #"<TT>%-12345X\@PJL JOB NAME=\"</TT>",
  5127.          #"<I><A job name></I>",
  5128.          #"<TT>\"</TT><BR>");
  5129.     for my $command (@pjlcommands) {
  5130.         push(@pjltmp,
  5131.          "<TT>$command</TT><BR>");
  5132.     }
  5133.     push(@pjltmp,
  5134.          "<I><The job data></I><BR>",
  5135.          "<I><ESC></I>",
  5136.          # The "JOB" PJL command is not supported by all printers
  5137.          "<TT>%-12345X\@PJL RESET</TT></BLOCKQUOTE><P>",
  5138.          #"<TT>%-12345X\@PJL EOJ</TT></BLOCKQUOTE><P>",
  5139.          "<I><ESC></I>",
  5140.          ": This is the ",
  5141.          "<I>ESC</I>",
  5142.          " character, ASCII code 27.<P>",
  5143.          #"<I><A job name></I>",
  5144.          #": The job name can be chosen arbitrarily, some printers show it on their front panel displays.<P>",
  5145.          "It is not required to give the PJL arguments, you can leave out some of them or you can even send only the job data without PJL header and PJL end-of-job mark.<P>");
  5146.     push(@docs, "<B>PJL</B><P>");
  5147.     push(@docs, @pjltmp);
  5148.     } elsif ((defined($dat->{'drivernopjl'})) && 
  5149.          ($dat->{'drivernopjl'} == 1) && 
  5150.          (defined($dat->{'pjl'}))) {
  5151.     my @pjltmp;
  5152.     push(@pjltmp,
  5153.          "This driver produces a PJL header with PJL commands internally and it is incompatible with extra PJL options merged into that header. Therefore there are no PJL options available when using this driver.<P>");
  5154.     push(@docs, "<B>PJL</B><P>");
  5155.     push(@docs, @pjltmp);
  5156.     }
  5157.  
  5158.     push(@docs, "<B>Options</B><P>");
  5159.  
  5160.     push(@docs, @doctmp);
  5161.  
  5162.     return @docs;
  5163.    
  5164. }
  5165.  
  5166. # Get a shorter summary documentation thing.
  5167. sub get_summarydocs {
  5168.     my ($this) = $_[0];
  5169.  
  5170.     my $dat = $this->{'dat'};
  5171.  
  5172.     my @docs;
  5173.  
  5174.     for my $arg (@{$dat->{'args'}}) {
  5175.  
  5176.     # Make sure that the longname/translation exists
  5177.     if (!$arg->{'comment'}) {
  5178.         $arg->{'comment'} = longname($arg->{'name'});
  5179.     }
  5180.  
  5181.     my ($name,
  5182.         $required,
  5183.         $type,
  5184.         $comment,
  5185.         $spot,
  5186.         $default) = ($arg->{'name'},
  5187.              $arg->{'required'},
  5188.              $arg->{'type'},
  5189.              $arg->{'comment'},
  5190.              $arg->{'spot'},
  5191.              $arg->{'default'});
  5192.     
  5193.     my $reqstr = ($required ? " required" : "n optional");
  5194.     push(@docs,
  5195.          "Option `$name':\n  A$reqstr $type argument.\n  $comment\n");
  5196.  
  5197.     push(@docs,
  5198.          "  This option corresponds to a PJL command.\n") 
  5199.         if ($spot eq 'Y');
  5200.     
  5201.     if ($type eq 'bool') {
  5202.         if (defined($default)) {
  5203.         my $defstr = ($default ? "True" : "False");
  5204.         push(@docs, "  Default: $defstr\n");
  5205.         }
  5206.         push(@docs, "  Example (true): `$name'\n");
  5207.         push(@docs, "  Example (false): `no$name'\n");
  5208.     } elsif ($type eq 'enum') {
  5209.         push(@docs, "  Possible choices:\n");
  5210.         my $exarg;
  5211.         for (@{$arg->{'vals'}}) {
  5212.  
  5213.         # Make sure that the longname/translation exists
  5214.         if (!$_->{'comment'}) {
  5215.             $_->{'comment'} = longname($_->{'value'});
  5216.         }
  5217.  
  5218.         my ($choice, $comment) = ($_->{'value'}, $_->{'comment'});
  5219.         push(@docs, "   * $choice: $comment\n");
  5220.         $exarg=$choice;
  5221.         }
  5222.         if (defined($default)) {
  5223.         push(@docs, "  Default: $default\n");
  5224.         }
  5225.         push(@docs, "  Example: `$name=$exarg'\n");
  5226.     } elsif ($type eq 'int' or $type eq 'float') {
  5227.         my ($max, $min) = ($arg->{'max'}, $arg->{'min'});
  5228.         my $exarg;
  5229.         if (defined($max)) {
  5230.         push(@docs, "  Range: $min <= x <= $max\n");
  5231.         $exarg=$max;
  5232.         }
  5233.         if (defined($default)) {
  5234.         push(@docs, "  Default: $default\n");
  5235.         $exarg=$default;
  5236.         }
  5237.         if (!$exarg) { $exarg=0; }
  5238.         push(@docs, "  Example: `$name=$exarg'\n");
  5239.     }
  5240.  
  5241.     push(@docs, "\n");
  5242.     }
  5243.  
  5244.     return @docs;
  5245.  
  5246. }
  5247.  
  5248. # About as obsolete as the other docs functions.  Why on earth are
  5249. # there three, anyway?!
  5250. sub getdocs {
  5251.     my ($this) = $_[0];
  5252.  
  5253.     my $dat = $this->{'dat'};
  5254.  
  5255.     my @docs;
  5256.  
  5257.     for my $arg (@{$dat->{'args'}}) {
  5258.  
  5259.     # Make sure that the longname/translation exists
  5260.     if (!$arg->{'comment'}) {
  5261.         $arg->{'comment'} = longname($arg->{'name'});
  5262.     }
  5263.  
  5264.     my ($name,
  5265.         $required,
  5266.         $type,
  5267.         $comment,
  5268.         $spot,
  5269.         $default) = ($arg->{'name'},
  5270.              $arg->{'required'},
  5271.              $arg->{'type'},
  5272.              $arg->{'comment'},
  5273.              $arg->{'spot'},
  5274.              $arg->{'default'});
  5275.     
  5276.     my $reqstr = ($required ? " required" : "n optional");
  5277.     push(@docs,
  5278.          "Option `$name':\n  A$reqstr $type argument.\n  $comment\n");
  5279.  
  5280.     push(@docs,
  5281.          "  This option corresponds to a PJL command.\n") 
  5282.         if ($spot eq 'Y');
  5283.     
  5284.     if ($type eq 'bool') {
  5285.         if (defined($default)) {
  5286.         my $defstr = ($default ? "True" : "False");
  5287.         push(@docs, "  Default: $defstr\n");
  5288.         }
  5289.         push(@docs, "  Example (true): `$name'\n");
  5290.         push(@docs, "  Example (false): `no$name'\n");
  5291.     } elsif ($type eq 'enum') {
  5292.         push(@docs, "  Possible choices:\n");
  5293.         my $exarg;
  5294.         for (@{$arg->{'vals'}}) {
  5295.  
  5296.         # Make sure that the longname/translation exists
  5297.         if (!$_->{'comment'}) {
  5298.             $_->{'comment'} = longname($_->{'value'});
  5299.         }
  5300.  
  5301.         my ($choice, $comment) = ($_->{'value'}, $_->{'comment'});
  5302.         push(@docs, "   * $choice: $comment\n");
  5303.         $exarg=$choice;
  5304.         }
  5305.         if (defined($default)) {
  5306.         push(@docs, "  Default: $default\n");
  5307.         }
  5308.         push(@docs, "  Example: `$name=$exarg'\n");
  5309.     } elsif ($type eq 'int' or $type eq 'float') {
  5310.         my ($max, $min) = ($arg->{'max'}, $arg->{'min'});
  5311.         my $exarg;
  5312.         if (defined($max)) {
  5313.         push(@docs, "  Range: $min <= x <= $max\n");
  5314.         $exarg=$max;
  5315.         }
  5316.         if (defined($default)) {
  5317.         push(@docs, "  Default: $default\n");
  5318.         $exarg=$default;
  5319.         }
  5320.         if (!$exarg) { $exarg=0; }
  5321.         push(@docs, "  Example: `$name=$exarg'\n");
  5322.     }
  5323.  
  5324.     push(@docs, "\n");
  5325.     }
  5326.  
  5327.     return @docs;
  5328.  
  5329. }
  5330.  
  5331. # Find a choice value hash by name.
  5332. # Operates on old dat structure...
  5333. sub valbyname {
  5334.     my ($arg,$name) = @_;
  5335.  
  5336.     my $val;
  5337.     for my $val (@{$arg->{'vals'}}) {
  5338.     return $val if (lc($name) eq lc($val->{'value'}));
  5339.     }
  5340.  
  5341.     return undef;
  5342. }
  5343.  
  5344. # replace numbers with fixed 6-digit number for ease of sorting
  5345. # ie: sort { normalizename($a) cmp normalizename($b) } @foo;
  5346. sub normalizename {
  5347.     my $n = $_[0];
  5348.  
  5349.     $n =~ s/[\d\.]+/sprintf("%013.6f", $&)/eg;
  5350.     return $n;
  5351. }
  5352.  
  5353.  
  5354. # Load an XML object from the library
  5355. # You specify the relative file path (to .../db/), less the .xml on the end.
  5356. sub _get_object_xml {
  5357.     my ($this, $file, $quiet) = @_;
  5358.  
  5359.     open XML, "$libdir/db/$file.xml"
  5360.     or do { warn "Cannot open file $libdir/db/$file.xml\n"
  5361.             if !$quiet;
  5362.         return undef; };
  5363.     my $xml = join('', (<XML>));
  5364.     close XML;
  5365.  
  5366.     return $xml;
  5367. }
  5368.  
  5369. # Write an XML object from the library
  5370. # You specify the relative file path (to .../db/), less the .xml on the end.
  5371. sub _set_object_xml {
  5372.     my ($this, $file, $stuff, $cache) = @_;
  5373.  
  5374.     my $dir = "$libdir/db";
  5375.     my $xfile = "$dir/$file.xml";
  5376.     umask 0002;
  5377.     open XML, ">$xfile.$$"
  5378.     or do { warn "Cannot write file $xfile.$$\n";
  5379.         return undef; };
  5380.     print XML $stuff;
  5381.     close XML;
  5382.     rename "$xfile.$$", $xfile
  5383.     or die "Cannot rename $xfile.$$ to $xfile\n";
  5384.  
  5385.     return 1;
  5386. }
  5387.  
  5388. # Get a list of XML filenames from a library directory.  These could then be
  5389. # read with _get_object_xml.
  5390. sub _get_xml_filelist {
  5391.     my ($this, $dir) = @_;
  5392.  
  5393.     if (!defined($this->{"names-$dir"})) {
  5394.     opendir DRV, "$libdir/db/$dir"
  5395.         or die 'Cannot find source db for $dir\n';
  5396.     my $driverfile;
  5397.     while($driverfile = readdir(DRV)) {
  5398.         next if ($driverfile !~ m!^(.+)\.xml$!);
  5399.         push(@{$this->{"names-$dir"}}, $1);
  5400.     }
  5401.     closedir(DRV);
  5402.     }
  5403.  
  5404.     return @{$this->{"names-$dir"}};
  5405. }
  5406.  
  5407.  
  5408. # Return a Perl structure in eval-able ascii format
  5409. sub getascii {
  5410.     my ($this) = $_[0];
  5411.     if (! $this->{'dat'}) {
  5412.     $this->getdat();
  5413.     }
  5414.     
  5415.     local $Data::Dumper::Purity=1;
  5416.     local $Data::Dumper::Indent=1;
  5417.  
  5418.     # Encase data for inclusion in PPD file
  5419.     return Dumper($this->{'dat'});
  5420. }
  5421.  
  5422. # Return list of printer makes
  5423. sub get_makes {
  5424.     my ($this) = @_;
  5425.  
  5426.     my @makes;
  5427.     my %seenmakes;
  5428.     my $p;
  5429.     for $p (@{$this->get_overview()}) {
  5430.     my $make = $p->{'make'};
  5431.     push (@makes, $make) 
  5432.         if ! $seenmakes{$make}++;
  5433.     }
  5434.     
  5435.     return @makes;
  5436.     
  5437. }
  5438.  
  5439. # get a list of model names from a make
  5440. sub get_models_by_make {
  5441.     my ($this, $wantmake) = @_;
  5442.  
  5443.     my $over = $this->get_overview();
  5444.  
  5445.     my @models;
  5446.     my $p;
  5447.     for $p (@{$over}) {
  5448.     push (@models, $p->{'model'}) 
  5449.         if ($wantmake eq $p->{'make'});
  5450.     }
  5451.  
  5452.     return @models;
  5453. }
  5454.  
  5455. # get a printer id from a make/model
  5456. sub get_printer_from_make_model {
  5457.     my ($this, $wantmake, $wantmodel) = @_;
  5458.  
  5459.     my $over = $this->get_overview();
  5460.     my $p;
  5461.     for $p (@{$over}) {
  5462.     return $p->{'id'} if ($p->{'make'} eq $wantmake
  5463.                   and $p->{'model'} eq $wantmodel);
  5464.     }
  5465.  
  5466.     return undef;
  5467. }
  5468.  
  5469. sub get_javascript2 {
  5470.  
  5471.     my ($this, $models, $oids) = @_;
  5472.  
  5473.     my @swit;
  5474.     my $mak;
  5475.     my $else = "";
  5476.     my @makes;
  5477.     my %modelhash;
  5478.     my %oidhash;
  5479.     if ($models) {
  5480.     %modelhash = %{$models};
  5481.     @makes = sort(keys %modelhash);
  5482.     } else {
  5483.     @makes = ($this->get_makes());
  5484.     }
  5485.     if ($oids) {
  5486.     %oidhash = %{$oids};
  5487.     }
  5488.     for $mak (@makes) {
  5489.     push (@swit,
  5490.           " $else if (make == \"$mak\") {\n");
  5491.  
  5492.     my $ct = 0;
  5493.  
  5494.     my @makemodels;
  5495.     if ($models) {
  5496.         @makemodels = @{$modelhash{$mak}};
  5497.     } else {
  5498.         @makemodels = ($this->get_models_by_make($mak));
  5499.     }
  5500.     my $mod;
  5501.     for $mod (sort {normalizename($a) cmp normalizename($b) } 
  5502.           @makemodels) {
  5503.         
  5504.         my $p;
  5505.         $p = $this->get_printer_from_make_model($mak, $mod);
  5506.         if (defined($p)) {
  5507.         push (@swit,
  5508.               "      o[i++]=new Option(\"$mod\", \"$p\");\n");
  5509.         $ct++;
  5510.         } else {
  5511.         my $oid;
  5512.         if ($oids) {
  5513.             $oid = $oidhash{$mak}{$mod};
  5514.         } else {
  5515.             $oid = "$mak-$mod";
  5516.             $oid =~ s/ /_/g;
  5517.             $oid =~ s/\+/plus/g;
  5518.             $oid =~ s/[^A-Za-z0-9_\-]//g;
  5519.             $oid =~ s/__+/_/g;
  5520.             $oid =~ s/_$//;
  5521.         }
  5522.         push (@swit,
  5523.               "      o[i++]=new Option(\"$mod\", \"$oid\");\n");
  5524.         $ct++;
  5525.         }
  5526.     }
  5527.  
  5528.     if (!$ct) {
  5529.         push(@swit,
  5530.          "      o[i++]=new Option(\"No Printers\", \"0\");\n");
  5531.     }
  5532.  
  5533.     push (@swit,
  5534.           "    }");
  5535.     $else = "else";
  5536.     }
  5537.  
  5538.     my $switch = join('',@swit);
  5539.  
  5540.     my $javascript = '
  5541.        function reflectMake(makeselector, modelselector) {
  5542.      //
  5543.      // This function is called when makeselector changes
  5544.      // by an onchange thingy on the makeselector.
  5545.      //
  5546.  
  5547.      // Get the value of the OPTION that just changed
  5548.      selected_value=makeselector.options[makeselector.selectedIndex].value;
  5549.      // Get the text of the OPTION that just changed
  5550.      make=makeselector.options[makeselector.selectedIndex].text;
  5551.  
  5552.      o = new Array;
  5553.      i=0;
  5554.  
  5555.      ' . $switch . '    if (i==0) {
  5556.        alert("Error: that dropdown should do something, but it doesnt");
  5557.      } else {
  5558.        modelselector.length=o.length;
  5559.        for (i=0; i < o.length; i++) {
  5560.          modelselector.options[i]=o[i];
  5561.        }
  5562.        modelselector.options[0].selected=true;
  5563.      }
  5564.  
  5565.        }
  5566.      ';
  5567.  
  5568.     return $javascript;
  5569. }
  5570.  
  5571.  
  5572.  
  5573.  
  5574. # Modify comments text to contain only what it should:
  5575. #
  5576. # <a>, <p>, <br> (<br> -> <p>)
  5577. #
  5578. sub comment_filter {
  5579.     my ($text) = @_;
  5580.  
  5581.     my $fake = ("INSERTFIXEDTHINGHERE" . sprintf("%06x", rand(1000000)));
  5582.     my %replacements;
  5583.     my $num = 1;
  5584.  
  5585.     # extract all the A href tags
  5586.     my $replace = "ANCHOR$fake$num";
  5587.     while ($text =~ 
  5588.        s!(<\s*a\s+href\s*=\s*['"]([^'"]+)['"]\s*>)!$replace!i) {
  5589.     $replacements{$replace} = $1;
  5590.     $num++;
  5591.     $replace = "ANCHOR$fake$num";
  5592.     }
  5593.  
  5594.     # extract all the A tail tags
  5595.     $replace = "ANCHORTAIL$fake$num";
  5596.     while ($text =~ 
  5597.        s!(<\s*/\s*a\s*>)!$replace!i) {
  5598.     $replacements{$replace} = $1;
  5599.     $num++;
  5600.     $replace = "ANCHOR$fake$num";
  5601.     }
  5602.  
  5603.     # extract all the P tags
  5604.     $replace = "PARA$fake$num";
  5605.     while ($text =~ 
  5606.        s!(<\s*p\s*>)!$replace!i) {
  5607.  
  5608.     $replacements{$replace} = $1;
  5609.     $num++;
  5610.     $replace = "PARA$fake$num";
  5611.     }
  5612.  
  5613.     # extract all the BR tags
  5614.     $replace = "PARA$fake$num";
  5615.     while ($text =~ 
  5616.        s!(<\s*br\s*>)!$replace!i) {
  5617.  
  5618.     $replacements{$replace} = $1;
  5619.     $num++;
  5620.     $replace = "PARA$fake$num";
  5621.     }
  5622.  
  5623.     # Now it's just clean text; remove all tags and &foo;s
  5624.     $text =~ s!<[^>]+>! !g;
  5625.     $text =~ s!&!&!g;
  5626.     $text =~ s!<!<!g;
  5627.     $text =~ s!>!>!g;
  5628.     $text =~ s!&[^;]+?;! !g;
  5629.  
  5630.     # Now rewrite into our teeny-html subset
  5631.     $text =~ s!&!&!g;
  5632.     $text =~ s!<!<!g;
  5633.     $text =~ s!>!>!g;
  5634.  
  5635.     # And reinsert the few things we wanted to preserve
  5636.     for (keys(%replacements)) {
  5637.     my ($k, $r) = ($_, $replacements{$_});
  5638.     $text =~ s!$k!$r!;
  5639.     }
  5640.  
  5641. #    print STDERR "$text";
  5642.  
  5643.     return $text;
  5644. }
  5645.  
  5646. 1;
  5647.